Add localization support. Fixes #230

This is less flexible, but it is an Adventure-native solution and doesn't require an onerous amount of work to maintain.
This commit is contained in:
Andrew Steinborn
2021-04-17 08:30:31 -04:00
parent 5d07c29cf6
commit c0b6f461cb
15 changed files with 247 additions and 196 deletions

View File

@@ -78,6 +78,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -90,7 +91,11 @@ import java.util.function.IntFunction;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.audience.ForwardingAudience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import net.kyori.adventure.util.UTF8ResourceBundleControl;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.AsyncHttpClient;
@@ -193,6 +198,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
logger.info("Booting up {} {}...", version().getName(), version().getVersion()); logger.info("Booting up {} {}...", version().getName(), version().getVersion());
console.setupStreams(); console.setupStreams();
registerTranslations();
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
cm.logChannelInformation(); cm.logChannelInformation();
@@ -236,6 +243,15 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics()); Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
} }
private void registerTranslations() {
final TranslationRegistry translationRegistry = TranslationRegistry
.create(Key.key("velocity", "translations"));
translationRegistry.registerAll(Locale.US,
ResourceBundle.getBundle("com/velocitypowered/proxy/messages", Locale.US,
UTF8ResourceBundleControl.get()), false);
GlobalTranslator.get().addSource(translationRegistry);
}
@SuppressFBWarnings("DM_EXIT") @SuppressFBWarnings("DM_EXIT")
private void doStartupConfigLoad() { private void doStartupConfigLoad() {
try { try {

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.command.builtin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.format.NamedTextColor;
public class CommandMessages {
public static final TranslatableComponent PLAYERS_ONLY = Component.translatable(
"velocity.command.players-only", NamedTextColor.RED);
public static final TranslatableComponent SERVER_DOES_NOT_EXIST = Component.translatable(
"velocity.command.server-does-not-exist", NamedTextColor.RED);
}

View File

@@ -78,11 +78,7 @@ public class GlistCommand {
final CommandSource source = context.getSource(); final CommandSource source = context.getSource();
sendTotalProxyCount(source); sendTotalProxyCount(source);
source.sendMessage(Identity.nil(), source.sendMessage(Identity.nil(),
Component.text().content("To view all players on servers, use ") Component.translatable("velocity.command.glist-view-all", NamedTextColor.YELLOW));
.color(NamedTextColor.YELLOW)
.append(Component.text("/glist all", NamedTextColor.DARK_AQUA))
.append(Component.text(".", NamedTextColor.YELLOW))
.build());
return 1; return 1;
} }
@@ -98,7 +94,7 @@ public class GlistCommand {
Optional<RegisteredServer> registeredServer = server.server(serverName); Optional<RegisteredServer> registeredServer = server.server(serverName);
if (!registeredServer.isPresent()) { if (!registeredServer.isPresent()) {
source.sendMessage(Identity.nil(), source.sendMessage(Identity.nil(),
Component.text("Server " + serverName + " doesn't exist.", NamedTextColor.RED)); CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)));
return -1; return -1;
} }
sendServerPlayers(source, registeredServer.get(), false); sendServerPlayers(source, registeredServer.get(), false);
@@ -107,10 +103,19 @@ public class GlistCommand {
} }
private void sendTotalProxyCount(CommandSource target) { private void sendTotalProxyCount(CommandSource target) {
int online = server.countConnectedPlayers();
target.sendMessage(Identity.nil(), Component.text() target.sendMessage(Identity.nil(), Component.text()
.content("There are ").color(NamedTextColor.YELLOW) .append(Component.text(Integer.toString(online), NamedTextColor.GREEN))
.append(Component.text(server.connectedPlayers().size(), NamedTextColor.GREEN)) .append(Component.space())
.append(Component.text(" player(s) online.", NamedTextColor.YELLOW)) .append(Component.translatable(
online == 1
? "velocity.command.glist-player-singular"
: "velocity.command.glist-player-plural",
NamedTextColor.YELLOW
))
.append(Component.space())
.append(Component.translatable("velocity.command.glist-total-suffix",
NamedTextColor.YELLOW))
.build()); .build());
} }

View File

@@ -35,6 +35,7 @@ import java.util.stream.Stream;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@@ -53,8 +54,7 @@ public class ServerCommand implements SimpleCommand {
final String[] args = invocation.arguments(); final String[] args = invocation.arguments();
if (!(source instanceof Player)) { if (!(source instanceof Player)) {
source.sendMessage(Identity.nil(), Component.text("Only players may run this command.", source.sendMessage(Identity.nil(), CommandMessages.PLAYERS_ONLY);
NamedTextColor.RED));
return; return;
} }
@@ -64,8 +64,8 @@ public class ServerCommand implements SimpleCommand {
String serverName = args[0]; String serverName = args[0];
Optional<RegisteredServer> toConnect = server.server(serverName); Optional<RegisteredServer> toConnect = server.server(serverName);
if (!toConnect.isPresent()) { if (!toConnect.isPresent()) {
player.sendMessage(Identity.nil(), player.sendMessage(Identity.nil(), CommandMessages.SERVER_DOES_NOT_EXIST
Component.text("Server " + serverName + " doesn't exist.", NamedTextColor.RED)); .args(Component.text(serverName)));
return; return;
} }
@@ -78,19 +78,23 @@ public class ServerCommand implements SimpleCommand {
private void outputServerInformation(Player executor) { private void outputServerInformation(Player executor) {
String currentServer = executor.connectedServer().map(ServerConnection::serverInfo) String currentServer = executor.connectedServer().map(ServerConnection::serverInfo)
.map(ServerInfo::name).orElse("<unknown>"); .map(ServerInfo::name).orElse("<unknown>");
executor.sendMessage(Identity.nil(), Component.text( executor.sendMessage(Identity.nil(), Component.translatable(
"You are currently connected to " + currentServer + ".", NamedTextColor.YELLOW)); "velocity.command.server-current-server",
NamedTextColor.YELLOW,
Component.text(currentServer)));
List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server); List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
if (servers.size() > MAX_SERVERS_TO_LIST) { if (servers.size() > MAX_SERVERS_TO_LIST) {
executor.sendMessage(Identity.nil(), Component.text( executor.sendMessage(Identity.nil(), Component.translatable(
"Too many servers to list. Tab-complete to show all servers.", NamedTextColor.RED)); "velocity.command.server-too-many", NamedTextColor.RED));
return; return;
} }
// Assemble the list of servers as components // Assemble the list of servers as components
TextComponent.Builder serverListBuilder = Component.text().content("Available servers: ") TextComponent.Builder serverListBuilder = Component.text()
.color(NamedTextColor.YELLOW); .append(Component.translatable("velocity.command.server-available",
NamedTextColor.YELLOW))
.append(Component.space());
for (int i = 0; i < servers.size(); i++) { for (int i = 0; i < servers.size(); i++) {
RegisteredServer rs = servers.get(i); RegisteredServer rs = servers.get(i);
serverListBuilder.append(formatServerComponent(currentServer, rs)); serverListBuilder.append(formatServerComponent(currentServer, rs));
@@ -106,17 +110,31 @@ public class ServerCommand implements SimpleCommand {
ServerInfo serverInfo = server.serverInfo(); ServerInfo serverInfo = server.serverInfo();
TextComponent serverTextComponent = Component.text(serverInfo.name()); TextComponent serverTextComponent = Component.text(serverInfo.name());
String playersText = server.connectedPlayers().size() + " player(s) online"; int connectedPlayers = server.connectedPlayers().size();
TranslatableComponent playersTextComponent;
if (connectedPlayers == 1) {
playersTextComponent = Component.translatable("velocity.command.server-tooltip-player-online");
} else {
playersTextComponent = Component.translatable("velocity.command.server-tooltip-players-online");
}
playersTextComponent = playersTextComponent.args(Component.text(connectedPlayers));
if (serverInfo.name().equals(currentPlayerServer)) { if (serverInfo.name().equals(currentPlayerServer)) {
serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN) serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN)
.hoverEvent( .hoverEvent(
showText(Component.text("Currently connected to this server\n" + playersText)) showText(
Component.translatable("velocity.command.server-tooltip-current-server")
.append(Component.newline())
.append(playersTextComponent))
); );
} else { } else {
serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY) serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY)
.clickEvent(ClickEvent.runCommand("/server " + serverInfo.name())) .clickEvent(ClickEvent.runCommand("/server " + serverInfo.name()))
.hoverEvent( .hoverEvent(
showText(Component.text("Click to connect to this server\n" + playersText)) showText(
Component.translatable("velocity.command.server-tooltip-offer-connect-server")
.append(Component.newline())
.append(playersTextComponent))
); );
} }
return serverTextComponent; return serverTextComponent;

View File

@@ -17,7 +17,6 @@
package com.velocitypowered.proxy.command.builtin; package com.velocitypowered.proxy.command.builtin;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@@ -49,6 +48,7 @@ import java.util.stream.Collectors;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@@ -180,17 +180,15 @@ public class VelocityCommand implements SimpleCommand {
public void execute(CommandSource source, String @NonNull [] args) { public void execute(CommandSource source, String @NonNull [] args) {
try { try {
if (server.reloadConfiguration()) { if (server.reloadConfiguration()) {
source.sendMessage(Identity.nil(), Component.text( source.sendMessage(Component.translatable("velocity.command.reload-success",
"Configuration reloaded.", NamedTextColor.GREEN)); NamedTextColor.GREEN));
} else { } else {
source.sendMessage(Identity.nil(), Component.text( source.sendMessage(Component.translatable("velocity.command.reload-failure",
"Unable to reload your configuration. Check the console for more details.",
NamedTextColor.RED)); NamedTextColor.RED));
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to reload configuration", e); logger.error("Unable to reload configuration", e);
source.sendMessage(Identity.nil(), Component.text( source.sendMessage(Component.translatable("velocity.command.reload-failure",
"Unable to reload your configuration. Check the console for more details.",
NamedTextColor.RED)); NamedTextColor.RED));
} }
} }
@@ -219,33 +217,35 @@ public class VelocityCommand implements SimpleCommand {
ProxyVersion version = server.version(); ProxyVersion version = server.version();
TextComponent velocity = Component.text().content(version.getName() + " ") Component velocity = Component.text().content(version.getName() + " ")
.decoration(TextDecoration.BOLD, true) .decoration(TextDecoration.BOLD, true)
.color(VELOCITY_COLOR) .color(VELOCITY_COLOR)
.append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false)) .append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false))
.build(); .build();
TextComponent copyright = Component Component copyright = Component
.text("Copyright 2018-2020 " + version.getVendor() + ". " + version.getName() .translatable("velocity.command.version-copyright",
+ " is licensed under the terms of the GNU General Public License v3."); Component.text(version.getVendor()),
Component.text(version.getName()));
source.sendMessage(Identity.nil(), velocity); source.sendMessage(Identity.nil(), velocity);
source.sendMessage(Identity.nil(), copyright); source.sendMessage(Identity.nil(), copyright);
if (version.getName().equals("Velocity")) { if (version.getName().equals("Velocity")) {
TextComponent velocityWebsite = Component.text() TextComponent embellishment = Component.text()
.content("Visit the ") .append(Component.text().content("velocitypowered.com")
.append(Component.text().content("Velocity website")
.color(NamedTextColor.GREEN) .color(NamedTextColor.GREEN)
.decoration(TextDecoration.UNDERLINED, true)
.clickEvent( .clickEvent(
ClickEvent.openUrl("https://www.velocitypowered.com")) ClickEvent.openUrl("https://www.velocitypowered.com"))
.build()) .build())
.append(Component.text(" or the ")) .append(Component.text(" - "))
.append(Component.text().content("Velocity GitHub") .append(Component.text().content("GitHub")
.color(NamedTextColor.GREEN) .color(NamedTextColor.GREEN)
.decoration(TextDecoration.UNDERLINED, true)
.clickEvent(ClickEvent.openUrl( .clickEvent(ClickEvent.openUrl(
"https://github.com/VelocityPowered/Velocity")) "https://github.com/VelocityPowered/Velocity"))
.build()) .build())
.build(); .build();
source.sendMessage(Identity.nil(), velocityWebsite); source.sendMessage(Identity.nil(), embellishment);
} }
} }
@@ -274,12 +274,13 @@ public class VelocityCommand implements SimpleCommand {
int pluginCount = plugins.size(); int pluginCount = plugins.size();
if (pluginCount == 0) { if (pluginCount == 0) {
source.sendMessage(Identity.nil(), Component.text( source.sendMessage(Component.translatable("velocity.command.no-plugins",
"No plugins installed.", NamedTextColor.YELLOW)); NamedTextColor.YELLOW));
return; return;
} }
TextComponent.Builder output = Component.text().content("Plugins: ") TranslatableComponent.Builder output = Component.translatable()
.key("velocity.command.plugins-list")
.color(NamedTextColor.YELLOW); .color(NamedTextColor.YELLOW);
for (int i = 0; i < pluginCount; i++) { for (int i = 0; i < pluginCount; i++) {
PluginContainer plugin = plugins.get(i); PluginContainer plugin = plugins.get(i);
@@ -300,15 +301,21 @@ public class VelocityCommand implements SimpleCommand {
description.url().ifPresent(url -> { description.url().ifPresent(url -> {
hoverText.append(Component.newline()); hoverText.append(Component.newline());
hoverText.append(Component.text("Website: " + url)); hoverText.append(Component.translatable(
"velocity.command.plugin-tooltip-website",
Component.text(url)));
}); });
if (!description.authors().isEmpty()) { if (!description.authors().isEmpty()) {
hoverText.append(Component.newline()); hoverText.append(Component.newline());
if (description.authors().size() == 1) { if (description.authors().size() == 1) {
hoverText.append(Component.text("Author: " + description.authors().get(0))); hoverText.append(Component.translatable("velocity.command.plugin-tooltip-author",
Component.text(description.authors().get(0))));
} else { } else {
hoverText.append(Component.text("Authors: " + Joiner.on(", ") hoverText.append(
.join(description.authors()))); Component.translatable("velocity.command.plugin-tooltip-author",
Component.text(String.join(", ", description.authors()))
)
);
} }
} }
description.description().ifPresent(pdesc -> { description.description().ifPresent(pdesc -> {
@@ -352,8 +359,8 @@ public class VelocityCommand implements SimpleCommand {
JsonArray connectOrder = new JsonArray(); JsonArray connectOrder = new JsonArray();
List<String> attemptedConnectionOrder = ImmutableList.copyOf( List<String> attemptedConnectionOrder = ImmutableList.copyOf(
server.configuration().getAttemptConnectionOrder()); server.configuration().getAttemptConnectionOrder());
for (int i = 0; i < attemptedConnectionOrder.size(); i++) { for (String s : attemptedConnectionOrder) {
connectOrder.add(attemptedConnectionOrder.get(i)); connectOrder.add(s);
} }
JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.configuration()); JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.configuration());
@@ -446,7 +453,7 @@ public class VelocityCommand implements SimpleCommand {
e.getCause().printStackTrace(); e.getCause().printStackTrace();
} catch (JsonParseException e) { } catch (JsonParseException e) {
source.sendMessage(Component.text() source.sendMessage(Component.text()
.content("An error occurred on the Velocity-servers and the dump could not " .content("An error occurred on the Velocity servers and the dump could not "
+ "be completed. Please contact the Velocity staff about this problem. " + "be completed. Please contact the Velocity staff about this problem. "
+ "If you do, provide the details about this error from the Velocity " + "If you do, provide the details about this error from the Velocity "
+ "console or server log.") + "console or server log.")

View File

@@ -45,7 +45,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@@ -72,25 +71,23 @@ public class VelocityConfiguration implements ProxyConfig {
@Expose private final Advanced advanced; @Expose private final Advanced advanced;
@Expose private final Query query; @Expose private final Query query;
private final Metrics metrics; private final Metrics metrics;
private final Messages messages;
private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent;
private @Nullable Favicon favicon; private @Nullable Favicon favicon;
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
Query query, Metrics metrics, Messages messages) { Query query, Metrics metrics) {
this.servers = servers; this.servers = servers;
this.forcedHosts = forcedHosts; this.forcedHosts = forcedHosts;
this.advanced = advanced; this.advanced = advanced;
this.query = query; this.query = query;
this.metrics = metrics; this.metrics = metrics;
this.messages = messages;
} }
private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode,
boolean preventClientProxyConnections, boolean announceForge, boolean preventClientProxyConnections, boolean announceForge,
PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret,
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, Servers servers,
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics, Messages messages) { ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics) {
this.bind = bind; this.bind = bind;
this.motd = motd; this.motd = motd;
this.showMaxPlayers = showMaxPlayers; this.showMaxPlayers = showMaxPlayers;
@@ -106,7 +103,6 @@ public class VelocityConfiguration implements ProxyConfig {
this.advanced = advanced; this.advanced = advanced;
this.query = query; this.query = query;
this.metrics = metrics; this.metrics = metrics;
this.messages = messages;
} }
/** /**
@@ -373,10 +369,6 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isLogCommandExecutions(); return advanced.isLogCommandExecutions();
} }
public Messages getMessages() {
return messages;
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
@@ -450,7 +442,6 @@ public class VelocityConfiguration implements ProxyConfig {
CommentedConfig advancedConfig = config.get("advanced"); CommentedConfig advancedConfig = config.get("advanced");
CommentedConfig queryConfig = config.get("query"); CommentedConfig queryConfig = config.get("query");
CommentedConfig metricsConfig = config.get("metrics"); CommentedConfig metricsConfig = config.get("metrics");
CommentedConfig messagesConfig = config.get("messages");
PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode", PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode",
PlayerInfoForwarding.NONE); PlayerInfoForwarding.NONE);
PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough",
@@ -480,8 +471,7 @@ public class VelocityConfiguration implements ProxyConfig {
new ForcedHosts(forcedHostsConfig), new ForcedHosts(forcedHostsConfig),
new Advanced(advancedConfig), new Advanced(advancedConfig),
new Query(queryConfig), new Query(queryConfig),
new Metrics(metricsConfig), new Metrics(metricsConfig)
new Messages(messagesConfig, defaultConfig.get("messages"))
); );
} }
@@ -793,73 +783,4 @@ public class VelocityConfiguration implements ProxyConfig {
return enabled; return enabled;
} }
} }
public static class Messages {
private final CommentedConfig toml;
private final CommentedConfig defaultToml;
private final String kickPrefix;
private final String disconnectPrefix;
private final String onlineModeOnly;
private final String noAvailableServers;
private final String alreadyConnected;
private final String movedToNewServerPrefix;
private final String genericConnectionError;
private Messages(CommentedConfig toml, CommentedConfig defaultToml) {
this.toml = toml;
this.defaultToml = defaultToml;
this.kickPrefix = getString("kick-prefix");
this.disconnectPrefix = getString("disconnect-prefix");
this.onlineModeOnly = getString("online-mode-only");
this.noAvailableServers = getString("no-available-servers");
this.alreadyConnected = getString("already-connected");
this.movedToNewServerPrefix = getString("moved-to-new-server-prefix");
this.genericConnectionError = getString("generic-connection-error");
}
private String getString(String path) {
String def = defaultToml.getOrElse(path, "");
if (toml == null) {
return def;
}
return toml.getOrElse(path, def);
}
public Component getKickPrefix(String server) {
return deserialize(String.format(kickPrefix, server));
}
public Component getDisconnectPrefix(String server) {
return deserialize(String.format(disconnectPrefix, server));
}
public Component getOnlineModeOnly() {
return deserialize(onlineModeOnly);
}
public Component getNoAvailableServers() {
return deserialize(noAvailableServers);
}
public Component getAlreadyConnected() {
return deserialize(alreadyConnected);
}
public Component getMovedToNewServerPrefix() {
return deserialize(movedToNewServerPrefix);
}
public Component getGenericConnectionError() {
return deserialize(genericConnectionError);
}
private Component deserialize(String str) {
if (str.startsWith("{")) {
return GsonComponentSerializer.gson().deserialize(str);
}
return LegacyComponentSerializer.legacyAmpersand().deserialize(str);
}
}
} }

View File

@@ -50,8 +50,8 @@ import net.kyori.adventure.text.TextComponent;
public class LoginSessionHandler implements MinecraftSessionHandler { public class LoginSessionHandler implements MinecraftSessionHandler {
private static final TextComponent MODERN_IP_FORWARDING_FAILURE = Component private static final Component MODERN_IP_FORWARDING_FAILURE = Component
.text("Your server did not send a forwarding request to the proxy. Is it set up correctly?"); .translatable("velocity.error.modern-forwarding-failed");
private final VelocityServer server; private final VelocityServer server;
private final VelocityServerConnection serverConn; private final VelocityServerConnection serverConn;

View File

@@ -168,9 +168,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
.exceptionally(e -> { .exceptionally(e -> {
logger.info("Exception occurred while running command for {}", logger.info("Exception occurred while running command for {}",
player.username(), e); player.username(), e);
player.sendMessage(Identity.nil(), player.sendMessage(Component.translatable("velocity.command.generic-error",
Component.text("An error occurred while running this command.", NamedTextColor.RED));
NamedTextColor.RED));
return null; return null;
}); });
} else { } else {
@@ -335,7 +334,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void exception(Throwable throwable) { public void exception(Throwable throwable) {
player.disconnect(server.configuration().getMessages().getGenericConnectionError()); player.disconnect(Component.translatable("velocity.error.player-connection-error",
NamedTextColor.RED));
} }
@Override @Override

View File

@@ -446,18 +446,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
wrapped = cause; wrapped = cause;
} }
} }
String userMessage;
Component friendlyError;
if (connectedServer != null && connectedServer.serverInfo().equals(server.serverInfo())) { if (connectedServer != null && connectedServer.serverInfo().equals(server.serverInfo())) {
userMessage = "Your connection to " + server.serverInfo().name() + " encountered an " friendlyError = Component.translatable("velocity.error.connected-server-error",
+ "error."; Component.text(server.serverInfo().name()));
} else { } else {
logger.error("{}: unable to connect to server {}", this, server.serverInfo().name(), logger.error("{}: unable to connect to server {}", this, server.serverInfo().name(),
wrapped); wrapped);
userMessage = "Unable to connect to " + server.serverInfo().name() + ". Try again " friendlyError = Component.translatable("velocity.error.connecting-server-error",
+ "later."; Component.text(server.serverInfo().name()));
} }
handleConnectionException(server, null, Component.text(userMessage, handleConnectionException(server, null, friendlyError.color(NamedTextColor.RED), safe);
NamedTextColor.RED), safe);
} }
/** /**
@@ -473,26 +473,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return; return;
} }
VelocityConfiguration.Messages messages = this.server.configuration().getMessages();
Component disconnectReason = GsonComponentSerializer.gson().deserialize(disconnect.getReason()); Component disconnectReason = GsonComponentSerializer.gson().deserialize(disconnect.getReason());
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason); String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
if (connectedServer != null && connectedServer.serverInfo().equals(server.serverInfo())) { if (connectedServer != null && connectedServer.serverInfo().equals(server.serverInfo())) {
logger.error("{}: kicked from server {}: {}", this, server.serverInfo().name(), logger.error("{}: kicked from server {}: {}", this, server.serverInfo().name(),
plainTextReason); plainTextReason);
handleConnectionException(server, disconnectReason, Component.text() handleConnectionException(server, disconnectReason,
.append(messages.getKickPrefix(server.serverInfo().name()) Component.translatable("velocity.error.moved-to-new-server", NamedTextColor.RED,
.colorIfAbsent(NamedTextColor.RED)) Component.text(server.serverInfo().name()),
.color(NamedTextColor.RED) disconnectReason), safe);
.append(disconnectReason)
.build(), safe);
} else { } else {
logger.error("{}: disconnected while connecting to {}: {}", this, logger.error("{}: disconnected while connecting to {}: {}", this,
server.serverInfo().name(), plainTextReason); server.serverInfo().name(), plainTextReason);
handleConnectionException(server, disconnectReason, Component.text() handleConnectionException(server, disconnectReason,
.append(messages.getDisconnectPrefix(server.serverInfo().name()) Component.translatable("velocity.error.cant-connect", NamedTextColor.RED,
.colorIfAbsent(NamedTextColor.RED)) Component.text(server.serverInfo().name()),
.append(disconnectReason) disconnectReason), safe);
.build(), safe);
} }
} }
@@ -576,8 +572,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
protocolVersion()), ((Impl) status).isSafe()); protocolVersion()), ((Impl) status).isSafe());
break; break;
case SUCCESS: case SUCCESS:
sendMessage(Identity.nil(), server.configuration().getMessages() sendMessage(Component.translatable("velocity.error.moved-to-new-server",
.getMovedToNewServerPrefix().append(friendlyReason)); NamedTextColor.RED,
Component.text(originalEvent.server().serverInfo().name()),
friendlyReason));
break; break;
default: default:
// The only remaining value is successful (no need to do anything!) // The only remaining value is successful (no need to do anything!)

View File

@@ -70,7 +70,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(LegacyHandshakePacket packet) { public boolean handle(LegacyHandshakePacket packet) {
connection.closeWith(LegacyDisconnectPacket connection.closeWith(LegacyDisconnectPacket
.from(Component.text("Your client is old, please upgrade!", NamedTextColor.RED))); .from(Component.text(
"Your client is extremely old. Please update to a newer version of Minecraft.",
NamedTextColor.RED)
));
return true; return true;
} }
@@ -122,7 +125,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress(); InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
if (!server.getIpAttemptLimiter().attempt(address)) { if (!server.getIpAttemptLimiter().attempt(address)) {
ic.disconnectQuietly(Component.text("You are logging in too fast, try again later.")); ic.disconnectQuietly(Component.translatable("velocity.error.logging-in-too-fast"));
return; return;
} }
@@ -132,7 +135,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
// and lower, otherwise IP information will never get forwarded. // and lower, otherwise IP information will never get forwarded.
if (server.configuration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN if (server.configuration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().lt(ProtocolVersion.MINECRAFT_1_13)) { && handshake.getProtocolVersion().lt(ProtocolVersion.MINECRAFT_1_13)) {
ic.disconnectQuietly(Component.text("This server is only compatible with 1.13 and above.")); ic.disconnectQuietly(Component.translatable(
"velocity.error.modern-forwarding-needs-new-client"));
return; return;
} }

View File

@@ -65,6 +65,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.translation.GlobalTranslator; import net.kyori.adventure.translation.GlobalTranslator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -152,7 +153,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
GameProfile.class), true); GameProfile.class), true);
} else if (profileResponse.getStatusCode() == 204) { } else if (profileResponse.getStatusCode() == 204) {
// Apparently an offline-mode user logged onto this online-mode proxy. // Apparently an offline-mode user logged onto this online-mode proxy.
inbound.disconnect(server.configuration().getMessages().getOnlineModeOnly()); inbound.disconnect(Component.translatable("velocity.error.online-mode-only",
NamedTextColor.RED));
} else { } else {
// Something else went wrong // Something else went wrong
logger.error( logger.error(
@@ -192,9 +194,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
Optional<Component> disconnectReason = result.denialReason(); Optional<Component> disconnectReason = result.denialReason();
if (disconnectReason.isPresent()) { if (disconnectReason.isPresent()) {
// The component is guaranteed to be provided if the connection was denied. // The component is guaranteed to be provided if the connection was denied.
Component disconnectReasonTranslated = GlobalTranslator.render(disconnectReason.get(), inbound.disconnect(disconnectReason.get());
Locale.getDefault());
inbound.disconnect(disconnectReasonTranslated);
return; return;
} }
@@ -243,7 +243,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
mcConnection, inbound.connectedHost().orElse(null), onlineMode); mcConnection, inbound.connectedHost().orElse(null), onlineMode);
this.connectedPlayer = player; this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) { if (!server.canRegisterConnection(player)) {
player.disconnect0(server.configuration().getMessages().getAlreadyConnected(), true); player.disconnect0(Component.translatable("velocity.error.already-connected-proxy",
NamedTextColor.RED), true);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
@@ -304,8 +305,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
player.disconnect0(reason.get(), true); player.disconnect0(reason.get(), true);
} else { } else {
if (!server.registerConnection(player)) { if (!server.registerConnection(player)) {
player.disconnect0(server.configuration().getMessages() player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"),
.getAlreadyConnected(), true); true);
return; return;
} }
@@ -333,8 +334,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
.thenRunAsync(() -> { .thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.initialServer(); Optional<RegisteredServer> toTry = event.initialServer();
if (!toTry.isPresent()) { if (!toTry.isPresent()) {
player.disconnect0(server.configuration().getMessages() player.disconnect0(Component.translatable("velocity.error.no-available-servers",
.getNoAvailableServers(), true); NamedTextColor.RED), true);
return; return;
} }
player.createConnectionRequest(toTry.get()).fireAndForget(); player.createConnectionRequest(toTry.get()).fireAndForget();

View File

@@ -19,16 +19,17 @@ package com.velocitypowered.proxy.connection.util;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
public class ConnectionMessages { public class ConnectionMessages {
public static final TextComponent ALREADY_CONNECTED = Component public static final TranslatableComponent ALREADY_CONNECTED = Component
.text("You are already connected to this server!", NamedTextColor.RED); .translatable("velocity.error.already-connected", NamedTextColor.RED);
public static final TextComponent IN_PROGRESS = Component public static final TranslatableComponent IN_PROGRESS = Component
.text("You are already connecting to a server!", NamedTextColor.RED); .translatable("velocity.error.already-connecting", NamedTextColor.RED);
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = Component public static final TranslatableComponent INTERNAL_SERVER_CONNECTION_ERROR = Component
.text("An internal server connection error occurred.", NamedTextColor.RED); .translatable("velocity.error.internal-server-connection-error", NamedTextColor.RED);
private ConnectionMessages() { private ConnectionMessages() {
throw new AssertionError(); throw new AssertionError();

View File

@@ -26,9 +26,12 @@ import com.velocitypowered.api.permission.PermissionFunction;
import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import java.util.List; import java.util.List;
import java.util.Locale;
import net.kyori.adventure.identity.Identity; import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.minecrell.terminalconsole.SimpleTerminalConsole; import net.minecrell.terminalconsole.SimpleTerminalConsole;
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@@ -52,8 +55,8 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
@Override @Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message) { public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
logger.info(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection() Component translated = GlobalTranslator.render(message, Locale.getDefault());
.serialize(message)); logger.info(LegacyComponentSerializer.legacySection().serialize(translated));
} }
@Override @Override
@@ -114,7 +117,8 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
protected void runCommand(String command) { protected void runCommand(String command) {
try { try {
if (!this.server.commandManager().execute(this, command).join()) { if (!this.server.commandManager().execute(this, command).join()) {
sendMessage(Component.text("Command not found.", NamedTextColor.RED)); sendMessage(Component.translatable("velocity.command.command-does-not-exist",
NamedTextColor.RED));
} }
} catch (Exception e) { } catch (Exception e) {
logger.error("An error occurred while running this command.", e); logger.error("An error occurred while running this command.", e);

View File

@@ -0,0 +1,62 @@
#
# Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
#
velocity.error.already-connected=You are already connected to this server!
velocity.error.already-connected-proxy=You are already connected to this proxy!
velocity.error.already-connecting=You are already trying to connect to a server!
velocity.error.cant-connect=Unable to connect to {0}: {1}
velocity.error.connecting-server-error=Unable to connect you to {0}. Please try again later.
velocity.error.connected-server-error=Your connection to {0} encountered a problem.
velocity.error.internal-server-connection-error=An internal server connection error occurred.
velocity.error.logging-in-too-fast=You are logging in too fast, try again later.
velocity.error.online-mode-only=You are not logged into your Minecraft account. If you are logged into your Minecraft account, try restarting your Minecraft client.
velocity.error.player-connection-error=An internal error occurred in your connection.
velocity.error.modern-forwarding-needs-new-client=This server is only compatible with Minecraft 1.13 and above.
velocity.error.modern-forwarding-failed=Your server did not send a forwarding request to the proxy. Make sure the server is configured for Velocity forwarding.
velocity.error.moved-to-new-server=YOu were kicked from {0}: {1}
velocity.error.no-available-servers=There are no available servers to connect you to. Try again later or contact an admin.
# Commands
velocity.command.generic-error=An error occurred while running this command.
velocity.command.command-does-not-exist=This command does not exist.
velocity.command.players-only=Only players can run this command.
velocity.command.server-does-not-exist=The specified server {0} does not exist.
velocity.command.server-current-server=You are currently connected to {0}.
velocity.command.server-too-many=There are too many servers set up. Use tab completion to get individual server counts.
velocity.command.server-available=Available servers:
velocity.command.server-tooltip-player-online={0} player online
velocity.command.server-tooltip-players-online={0} players online
velocity.command.server-tooltip-current-server=Currently connected to this server
velocity.command.server-tooltip-offer-connect-server=Click to connect to this server
velocity.command.glist-player-singular=player
velocity.command.glist-player-plural=players
velocity.command.glist-total-suffix= are currently connected to the proxy.
velocity.command.glist-view-all=To view all players on servers, use /glist all.
velocity.command.reload-success=Velocity configuration successfully reloaded.
velocity.command.reload-failure=Unable to reload your Velocity configuration. Check the console for more details.
velocity.command.version-copyright=Copyright 2018-2021 {0}. {1} is licensed under the terms of the GNU General Public License v3.
velocity.command.no-plugins=There are no plugins currently installed.
velocity.command.plugins-list=Plugins: {0}
velocity.command.plugin-tooltip-website=Website: {0}
velocity.command.plugin-tooltip-author=Author: {0}
velocity.command.plugin-tooltip-authors=Authors: {0}

View File

@@ -142,19 +142,3 @@ map = "Velocity"
# Whether plugins should be shown in query response by default or not # Whether plugins should be shown in query response by default or not
show-plugins = false show-plugins = false
# Legacy color codes and JSON are accepted in all messages.
[messages]
# Prefix when the player gets kicked from a server.
# First argument '%s': the server name
kick-prefix = "&cKicked from %s: "
# Prefix when the player is disconnected from a server.
# First argument '%s': the server name
disconnect-prefix = "&cCan't connect to %s: "
online-mode-only = "&cThis server only accepts connections from online-mode clients.\n\n&7Did you change your username? Sign out of Minecraft, sign back in, and try again."
no-available-servers = "&cThere are no available servers."
already-connected = "&cYou are already connected to this proxy!"
moved-to-new-server-prefix = "&cThe server you were on kicked you: "
generic-connection-error = "&cAn internal error occurred in your connection."