From e90631b14d2510e5d5ef58a84fd5342fe0594e5c Mon Sep 17 00:00:00 2001 From: James Mortemore Date: Sat, 4 Apr 2026 14:11:14 +0100 Subject: [PATCH] feat: add hybrid localisation support with per-player locale resolution Adds multi-language support using a server-wide default locale with optional per-player override (similar to LuckPerms/EssentialsX). - Introduce MessageRegistry with immutable Snapshot for thread-safe atomic swaps during hot-reloads - Refactor Message class to use deferred resolution, applying token replacements at resolve time with locale-aware lookups - Add locale cascading fallback (e.g. zh_tw -> zh -> default) - Migrate messages from single messages.yml to messages/ directory with per-locale files (messages_en.yml, etc.), retaining messages.yml as a backward-compatible overlay - Add locale config (locale.default, locale.perPlayer) to config.yml - Persist player locale in bm_players table (VARCHAR(16)) via V2 database migration, gated by perPlayerLocale config - Add locale-aware kick(Message) and broadcast(Message, String) APIs to CommonPlayer and CommonServer - Migrate all kick() and broadcast() call sites (17 kick, 26 broadcast) to the new Message-based overloads - Implement platform-specific getLocale() across Bukkit, Bungee, Velocity, Sponge (API7+API8), and Fabric (with pre-1.21 fallback) - Add locale-aware DateUtils overloads for localised time formatting - Log loaded locales and missing key diagnostics on startup - Add comprehensive tests: MessageRegistry, Message deferred resolution, locale normalisation, player storage locale persistence, DB migration - Add E2E locale smoke test with German locale fixture - Update E2E sync-configs.sh to distribute messages/ directories --- .../banmanager/bukkit/BukkitPlayer.java | 8 + .../bukkit/listeners/JoinListener.java | 5 +- .../banmanager/bungee/BungeePlayer.java | 8 + .../bungee/listeners/JoinListener.java | 5 +- .../banmanager/common/BanManagerPlugin.java | 128 ++++- .../banmanager/common/CommonPlayer.java | 6 + .../banmanager/common/CommonServer.java | 10 + .../common/commands/BanCommand.java | 2 +- .../common/commands/BanIpCommand.java | 2 +- .../common/commands/BanIpRangeCommand.java | 2 +- .../common/commands/BanNameCommand.java | 2 +- .../common/commands/KickAllCommand.java | 4 +- .../common/commands/KickCommand.java | 4 +- .../commands/LoglessKickAllCommand.java | 4 +- .../common/commands/LoglessKickCommand.java | 4 +- .../common/commands/TempBanCommand.java | 2 +- .../common/commands/TempIpBanCommand.java | 2 +- .../commands/TempIpRangeBanCommand.java | 2 +- .../common/commands/TempNameBanCommand.java | 2 +- .../common/commands/TempWarnCommand.java | 2 +- .../common/commands/UnbanCommand.java | 2 +- .../common/commands/UnbanIpCommand.java | 2 +- .../common/commands/UnbanIpRangeCommand.java | 2 +- .../common/commands/UnbanNameCommand.java | 2 +- .../common/commands/UnmuteCommand.java | 2 +- .../common/commands/UnmuteIpCommand.java | 2 +- .../common/commands/WarnCommand.java | 2 +- .../common/configs/DefaultConfig.java | 6 + .../common/configs/MessagesConfig.java | 9 +- .../banmanager/common/data/PlayerData.java | 5 + .../common/listeners/CommonBanListener.java | 8 +- .../common/listeners/CommonChatListener.java | 4 +- .../common/listeners/CommonJoinListener.java | 47 +- .../common/listeners/CommonMuteListener.java | 4 +- .../common/listeners/CommonNoteListener.java | 2 +- .../listeners/CommonReportListener.java | 2 +- .../banmanager/common/runnables/BanSync.java | 2 +- .../common/runnables/GlobalBanSync.java | 2 +- .../common/runnables/GlobalIpSync.java | 2 +- .../banmanager/common/runnables/NameSync.java | 2 +- .../banmanager/common/util/DateUtils.java | 22 +- .../banmanager/common/util/Message.java | 131 +++-- .../common/util/MessageRegistry.java | 139 ++++++ common/src/main/resources/config.yml | 4 + .../db/local/V2__add_player_locale.sql | 1 + .../main/resources/db/local/migrations.list | 1 + .../main/resources/messages/messages_en.yml | 448 ++++++++++++++++++ .../banmanager/common/BasePluginTest.java | 13 + .../banmanager/common/TestPlayer.java | 15 + .../banmanager/common/TestServer.java | 5 + .../common/commands/KickAllCommandTest.java | 22 +- .../common/commands/KickCommandTest.java | 16 +- .../commands/LoglessKickAllCommandTest.java | 22 +- .../common/configs/ConfigReloadTest.java | 9 +- .../common/configs/MessagesConfigTest.java | 39 +- .../listeners/CommonJoinListenerTest.java | 3 + .../common/listeners/NoteListenerTest.java | 15 +- .../common/storage/PlayerStorageTest.java | 45 ++ .../migration/MigrationIntegrationTest.java | 2 +- .../common/util/LocaleNormalisationTest.java | 48 ++ .../common/util/MessageDeferredTest.java | 120 +++++ .../common/util/MessageRegistryTest.java | 135 ++++++ e2e/platforms/bukkit/configs/config.yml | 4 + .../bukkit/configs/messages/messages_de.yml | 16 + .../bukkit/configs/messages/messages_en.yml | 448 ++++++++++++++++++ .../bungee/configs/banmanager/config.yml | 4 + .../banmanager/messages/messages_de.yml | 16 + .../banmanager/messages/messages_en.yml | 448 ++++++++++++++++++ e2e/platforms/fabric/configs/config.yml | 4 + .../fabric/configs/messages/messages_de.yml | 16 + .../fabric/configs/messages/messages_en.yml | 448 ++++++++++++++++++ .../sponge/configs/banmanager/config.yml | 4 + .../banmanager/messages/messages_de.yml | 16 + .../banmanager/messages/messages_en.yml | 448 ++++++++++++++++++ .../sponge7/configs/banmanager/config.yml | 4 + .../banmanager/messages/messages_de.yml | 16 + .../banmanager/messages/messages_en.yml | 448 ++++++++++++++++++ .../velocity/configs/banmanager/config.yml | 4 + .../banmanager/messages/messages_de.yml | 16 + .../banmanager/messages/messages_en.yml | 448 ++++++++++++++++++ e2e/sync-configs.sh | 22 +- e2e/tests/src/locale-smoke.test.ts | 126 +++++ .../banmanager/fabric/FabricPlayer.java | 13 + .../fabric/listeners/JoinListener.java | 5 +- .../banmanager/sponge/SpongePlayer.java | 10 + .../sponge/listeners/JoinListener.java | 5 +- .../banmanager/sponge/SpongePlayer.java | 8 + .../sponge/listeners/JoinListener.java | 7 +- .../banmanager/velocity/VelocityPlayer.java | 8 + .../velocity/listeners/JoinListener.java | 2 +- 90 files changed, 4401 insertions(+), 181 deletions(-) create mode 100644 common/src/main/java/me/confuser/banmanager/common/util/MessageRegistry.java create mode 100644 common/src/main/resources/db/local/V2__add_player_locale.sql create mode 100644 common/src/main/resources/messages/messages_en.yml create mode 100644 common/src/test/java/me/confuser/banmanager/common/util/LocaleNormalisationTest.java create mode 100644 common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java create mode 100644 common/src/test/java/me/confuser/banmanager/common/util/MessageRegistryTest.java create mode 100644 e2e/platforms/bukkit/configs/messages/messages_de.yml create mode 100644 e2e/platforms/bukkit/configs/messages/messages_en.yml create mode 100644 e2e/platforms/bungee/configs/banmanager/messages/messages_de.yml create mode 100644 e2e/platforms/bungee/configs/banmanager/messages/messages_en.yml create mode 100644 e2e/platforms/fabric/configs/messages/messages_de.yml create mode 100644 e2e/platforms/fabric/configs/messages/messages_en.yml create mode 100644 e2e/platforms/sponge/configs/banmanager/messages/messages_de.yml create mode 100644 e2e/platforms/sponge/configs/banmanager/messages/messages_en.yml create mode 100644 e2e/platforms/sponge7/configs/banmanager/messages/messages_de.yml create mode 100644 e2e/platforms/sponge7/configs/banmanager/messages/messages_en.yml create mode 100644 e2e/platforms/velocity/configs/banmanager/messages/messages_de.yml create mode 100644 e2e/platforms/velocity/configs/banmanager/messages/messages_en.yml create mode 100644 e2e/tests/src/locale-smoke.test.ts diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java index d204b3d63..92d063378 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/BukkitPlayer.java @@ -7,6 +7,7 @@ import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.util.UUIDUtils; import net.md_5.bungee.chat.ComponentSerializer; import org.bukkit.Bukkit; @@ -125,6 +126,13 @@ public boolean isOnline() { return getPlayer() != null; } + @Override + public String getLocale() { + Player p = getPlayer(); + if (p == null) return "en"; + return MessageRegistry.normaliseLocale(p.getLocale()); + } + private Player getPlayer() { if (player != null) return player; if (isOnlineMode()) return Bukkit.getServer().getPlayer(uuid); diff --git a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java index acd283dc5..94b53202a 100644 --- a/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java +++ b/bukkit/src/main/java/me/confuser/banmanager/bukkit/listeners/JoinListener.java @@ -60,8 +60,9 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - - handleDeny(message); + String locale = player.getLocale() != null ? player.getLocale() : "en"; + event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_BANNED); + event.setKickMessage(BukkitServer.formatMessage(message.resolve(locale))); } @Override diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java index 1f8e7a381..820be083b 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/BungeePlayer.java @@ -6,6 +6,7 @@ import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.chat.ComponentSerializer; @@ -110,6 +111,13 @@ public boolean canSee(CommonPlayer player) { return true; } + @Override + public String getLocale() { + java.util.Locale locale = player.getLocale(); + if (locale == null) return "en"; + return MessageRegistry.normaliseLocale(locale.toString()); + } + private ProxiedPlayer getPlayer() { return ProxyServer.getInstance().getPlayer(uuid); } diff --git a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java index 3b35e4a09..032d2f249 100755 --- a/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java +++ b/bungee/src/main/java/me/confuser/banmanager/bungee/listeners/JoinListener.java @@ -58,8 +58,9 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - - handleDeny(message); + String locale = player.getLocale() != null ? player.getLocale() : "en"; + event.setCancelled(true); + event.setCancelReason(BungeeServer.formatMessage(message.resolve(locale))); } @Override diff --git a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java index ac0c8a452..04396fb83 100644 --- a/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java +++ b/common/src/main/java/me/confuser/banmanager/common/BanManagerPlugin.java @@ -14,6 +14,7 @@ import me.confuser.banmanager.common.ormlite.logger.LocalLog; import me.confuser.banmanager.common.ormlite.support.ConnectionSource; import me.confuser.banmanager.common.ormlite.support.DatabaseConnection; +import me.confuser.banmanager.common.configuration.file.YamlConfiguration; import me.confuser.banmanager.common.runnables.Runner; import me.confuser.banmanager.common.storage.*; import me.confuser.banmanager.common.storage.global.*; @@ -21,9 +22,11 @@ import me.confuser.banmanager.common.storage.mariadb.MariaDBDatabase; import me.confuser.banmanager.common.storage.mysql.MySQLDatabase; import me.confuser.banmanager.common.util.DriverManagerUtil; +import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.nio.file.Files; import java.sql.SQLException; import static java.lang.Long.parseLong; @@ -153,6 +156,9 @@ public class BanManagerPlugin { @Getter private PlaceholderResolver placeholderResolver; + @Getter + private MessageRegistry messageRegistry; + public BanManagerPlugin(PluginInfo pluginInfo, CommonLogger logger, File dataFolder, CommonScheduler scheduler, CommonServer server, CommonMetrics metrics) { this.pluginInfo = pluginInfo; this.logger = logger; @@ -268,11 +274,6 @@ public final void disable() { } public void setupConfigs() { - MessagesConfig newMessagesConfig = new MessagesConfig(dataFolder, logger); - if (!newMessagesConfig.load()) { - logger.warning("Failed to reload messages.yml, keeping previous messages"); - } - config = reloadConfig(new DefaultConfig(dataFolder, logger), config, "config.yml"); consoleConfig = reloadConfig(new ConsoleConfig(dataFolder, logger), consoleConfig, "console.yml"); schedulesConfig = reloadConfig(new SchedulesConfig(dataFolder, logger), schedulesConfig, "schedules.yml"); @@ -280,6 +281,119 @@ public void setupConfigs() { reasonsConfig = reloadConfig(new ReasonsConfig(dataFolder, logger), reasonsConfig, "reasons.yml"); geoIpConfig = reloadConfig(new GeoIpConfig(dataFolder, logger), geoIpConfig, "geoip.yml"); webhookConfig = reloadConfig(new WebhookConfig(dataFolder, logger), webhookConfig, "webhooks.yml"); + + loadMessages(); + } + + private void loadMessages() { + String defaultLocale = config != null ? config.getDefaultLocale() : "en"; + MessageRegistry newRegistry = new MessageRegistry(defaultLocale); + + copyMessagesDirectory(); + + File messagesDir = new File(dataFolder, "messages"); + if (messagesDir.exists() && messagesDir.isDirectory()) { + File[] files = messagesDir.listFiles((dir, name) -> + name.startsWith("messages_") && name.endsWith(".yml")); + + if (files != null) { + for (File file : files) { + String fileName = file.getName(); + String locale = fileName.substring("messages_".length(), fileName.length() - ".yml".length()); + loadLocaleFile(newRegistry, file, locale); + } + } + } + + File legacyMessages = new File(dataFolder, "messages.yml"); + if (legacyMessages.exists()) { + loadLocaleFile(newRegistry, legacyMessages, defaultLocale); + } + + if (!newRegistry.hasAnyMessages()) { + if (messageRegistry != null) { + logger.warning("No messages loaded, keeping previous messages"); + return; + } + } + + if (messageRegistry != null) { + messageRegistry.atomicSwap(newRegistry); + } else { + messageRegistry = newRegistry; + } + + Message.init(messageRegistry, logger); + + logLocaleInfo(); + } + + private void logLocaleInfo() { + if (messageRegistry == null) return; + + java.util.Set locales = messageRegistry.getAvailableLocales(); + logger.info("Loaded " + locales.size() + " locale(s): " + String.join(", ", locales)); + + String defaultLocale = messageRegistry.getDefaultLocale(); + for (String locale : locales) { + if (locale.equals(defaultLocale)) continue; + int missing = messageRegistry.getMissingKeyCount(locale); + if (missing > 0) { + logger.info("Locale '" + locale + "' is missing " + missing + " key(s) (will fall back to '" + defaultLocale + "')"); + } + } + } + + private void loadLocaleFile(MessageRegistry registry, File file, String locale) { + try { + YamlConfiguration conf = new YamlConfiguration(); + conf.load(file); + + if (conf.getConfigurationSection("messages") == null) { + logger.warning("Messages section not found in " + file.getName() + ", skipping"); + return; + } + + java.util.Map messages = new java.util.HashMap<>(); + + for (String key : conf.getConfigurationSection("messages").getKeys(true)) { + String value = conf.getString("messages." + key); + if (value != null) { + messages.put(key, value.replace("\\n", "\n").replaceAll("(?<=\\n)(?=\\n)", " ")); + } + } + + if (!messages.isEmpty()) { + java.util.Map existing = registry.getMessages(locale); + if (!existing.isEmpty()) { + java.util.Map merged = new java.util.HashMap<>(existing); + merged.putAll(messages); + registry.loadLocale(locale, merged); + } else { + registry.loadLocale(locale, messages); + } + } + } catch (Exception e) { + logger.warning("Failed to load " + file.getName(), e); + } + } + + private void copyMessagesDirectory() { + File messagesDir = new File(dataFolder, "messages"); + if (!messagesDir.exists()) { + messagesDir.mkdirs(); + } + + File defaultMessages = new File(messagesDir, "messages_en.yml"); + if (!defaultMessages.exists()) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream("messages/messages_en.yml")) { + if (in != null) { + Files.copy(in, defaultMessages.toPath()); + } + } catch (IOException e) { + logger.warning("Failed to copy default messages_en.yml", e); + } + } } /** diff --git a/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java b/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java index 338ffe1c3..0ad1fc2dc 100644 --- a/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java +++ b/common/src/main/java/me/confuser/banmanager/common/CommonPlayer.java @@ -12,6 +12,10 @@ public interface CommonPlayer extends CommonSender { void kick(String message); + default void kick(Message message) { + kick(message.resolveFor(this)); + } + void sendMessage(String message); void sendMessage(Message message); @@ -43,4 +47,6 @@ public interface CommonPlayer extends CommonSender { boolean teleport(CommonWorld world, double x, double y, double z, float pitch, float yaw); boolean canSee(CommonPlayer player); + + String getLocale(); } diff --git a/common/src/main/java/me/confuser/banmanager/common/CommonServer.java b/common/src/main/java/me/confuser/banmanager/common/CommonServer.java index 74af21dd2..212857aef 100644 --- a/common/src/main/java/me/confuser/banmanager/common/CommonServer.java +++ b/common/src/main/java/me/confuser/banmanager/common/CommonServer.java @@ -3,6 +3,7 @@ import me.confuser.banmanager.common.api.events.CommonEvent; import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.kyori.text.TextComponent; +import me.confuser.banmanager.common.util.Message; import java.util.UUID; @@ -17,6 +18,15 @@ public interface CommonServer { void broadcast(String message, String permission); + default void broadcast(Message message, String permission) { + for (CommonPlayer player : getOnlinePlayers()) { + if (player.hasPermission(permission)) { + player.sendMessage(message.resolveFor(player)); + } + } + getConsoleSender().sendMessage(message.toString()); + } + void broadcastJSON(TextComponent message, String permission); void broadcast(String message, String permission, CommonSender sender); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java index 275e28b15..33de448f7 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanCommand.java @@ -167,7 +167,7 @@ public boolean onCommand(CommonSender sender, CommandParser parser) { if (onlinePlayer != null) { final Message finalKickMessage = kickMessage; getPlugin().getScheduler().runSync(() -> { - onlinePlayer.kick(finalKickMessage.toString()); + onlinePlayer.kick(finalKickMessage); }); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java index f5cb640e8..9d663386d 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpCommand.java @@ -130,7 +130,7 @@ public boolean onCommand(CommonSender sender, CommandParser parser) { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (IPUtils.toIPAddress(onlinePlayer.getAddress()).equals(ip)) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java index 5c7428c73..4725fb019 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanIpRangeCommand.java @@ -82,7 +82,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (ban.inRange(IPUtils.toIPAddress(onlinePlayer.getAddress()))) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java index 954c2896d..abb288aad 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/BanNameCommand.java @@ -91,7 +91,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(name)) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/KickAllCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/KickAllCommand.java index 7e937db67..cbe571ad9 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/KickAllCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/KickAllCommand.java @@ -94,14 +94,14 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { .set("playerId", player.getUniqueId().toString()) .set("actor", actor.getName()); - player.kick(kickMessage.toString()); + player.kick(kickMessage); } if (isSilent || !sender.hasPermission("bm.notify.kick")) { message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.kick"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.kick"); }); }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/KickCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/KickCommand.java index 0736fdc1a..33cebaf20 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/KickCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/KickCommand.java @@ -82,13 +82,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { Message message = Message.get(reason.isEmpty() ? "kick.notify.noReason" : "kick.notify.reason"); message.set("player", player.getName()).set("actor", actor.getName()).set("reason", reason); - player.kick(kickMessage.toString()); + player.kick(kickMessage); if (isSilent || !sender.hasPermission("bm.notify.kick")) { message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.kick"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.kick"); }); if (getPlugin().getConfig().isKickLoggingEnabled()) { diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickAllCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickAllCommand.java index 182fd3660..36d07e644 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickAllCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickAllCommand.java @@ -57,14 +57,14 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { .set("playerId", player.getUniqueId().toString()) .set("actor", actor.getName()); - player.kick(kickMessage.toString()); + player.kick(kickMessage); } if (isSilent || !sender.hasPermission("bm.notify.kick")) { message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.kick"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.kick"); }); }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickCommand.java index 699de15e9..666661390 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/LoglessKickCommand.java @@ -79,13 +79,13 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { Message message = Message.get(reason.isEmpty() ? "kick.notify.noReason" : "kick.notify.reason"); message.set("player", player.getName()).set("actor", actor.getName()).set("reason", reason); - player.kick(kickMessage.toString()); + player.kick(kickMessage); if (isSilent || !sender.hasPermission("bm.notify.kick")) { message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.kick"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.kick"); }); }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java index b7e848eab..46264416c 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempBanCommand.java @@ -190,7 +190,7 @@ public void run() { if (onlinePlayer != null) { final Message finalKickMessage = kickMessage; getPlugin().getScheduler().runSync(() -> { - onlinePlayer.kick(finalKickMessage.toString()); + onlinePlayer.kick(finalKickMessage); }); } diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java index 9d24ab14a..c358e80ba 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpBanCommand.java @@ -148,7 +148,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (IPUtils.toIPAddress(onlinePlayer.getAddress()).equals(ip)) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java index 3c069e320..51831b09c 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempIpRangeBanCommand.java @@ -101,7 +101,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (ban.inRange(IPUtils.toIPAddress((onlinePlayer.getAddress())))) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java index 888c9cf4e..f9501f327 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempNameBanCommand.java @@ -112,7 +112,7 @@ public void run() { for (CommonPlayer onlinePlayer : getPlugin().getServer().getOnlinePlayers()) { if (onlinePlayer.getName().equalsIgnoreCase(name)) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java index ac830c16b..3276a734d 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/TempWarnCommand.java @@ -172,7 +172,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.tempwarn"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.tempwarn"); final List actionCommands; diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanCommand.java index ce53ca001..44235e227 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanCommand.java @@ -106,7 +106,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unban"); + getPlugin().getServer().broadcast(message, "bm.notify.unban"); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpCommand.java index 8ff8a4def..7df20c8d8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpCommand.java @@ -110,7 +110,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unbanip"); + getPlugin().getServer().broadcast(message, "bm.notify.unbanip"); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpRangeCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpRangeCommand.java index c7afb178e..a83e72ff8 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpRangeCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanIpRangeCommand.java @@ -98,7 +98,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unbaniprange"); + getPlugin().getServer().broadcast(message, "bm.notify.unbaniprange"); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanNameCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanNameCommand.java index af43d9861..5b24ab88c 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnbanNameCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnbanNameCommand.java @@ -74,7 +74,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unbanname"); + getPlugin().getServer().broadcast(message, "bm.notify.unbanname"); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteCommand.java index 1d46002be..d2804b9bf 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteCommand.java @@ -111,7 +111,7 @@ public void run() { } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unmute"); + getPlugin().getServer().broadcast(message, "bm.notify.unmute"); } getPlugin().getScheduler().runSync(() -> { diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteIpCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteIpCommand.java index 7bd7162eb..b2178e24f 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteIpCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/UnmuteIpCommand.java @@ -98,7 +98,7 @@ public boolean onCommand(final CommonSender sender, CommandParser parser) { } if (!parser.isSilent()) { - getPlugin().getServer().broadcast(message.toString(), "bm.notify.unmuteip"); + getPlugin().getServer().broadcast(message, "bm.notify.unmuteip"); } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java b/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java index 30ad696a9..ecfbb9ac2 100644 --- a/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java +++ b/common/src/main/java/me/confuser/banmanager/common/commands/WarnCommand.java @@ -151,7 +151,7 @@ public boolean onCommand(final CommonSender sender, CommandParser originalParser message.sendTo(sender); } - if (!isSilent) getPlugin().getServer().broadcast(message.toString(), "bm.notify.warn"); + if (!isSilent) getPlugin().getServer().broadcast(message, "bm.notify.warn"); final List actionCommands; diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/DefaultConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/DefaultConfig.java index ab5021310..926b6e4e3 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/DefaultConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/DefaultConfig.java @@ -76,6 +76,10 @@ public class DefaultConfig extends Config { @Getter private UUIDFetcher uuidFetcher; @Getter + private String defaultLocale; + @Getter + private boolean perPlayerLocale; + @Getter private String geyserPrefix; public DefaultConfig(File dataFolder, CommonLogger logger) { @@ -140,6 +144,8 @@ public void afterLoad() { uuidFetcher = new UUIDFetcher(idToName, nameToId); geyserPrefix = conf.getString("geyserPrefix", ""); + defaultLocale = conf.getString("locale.default", "en"); + perPlayerLocale = conf.getBoolean("locale.perPlayer", true); } public void handleBlockedCommands(BanManagerPlugin plugin, HashSet set) { diff --git a/common/src/main/java/me/confuser/banmanager/common/configs/MessagesConfig.java b/common/src/main/java/me/confuser/banmanager/common/configs/MessagesConfig.java index 804f6b5f2..995aa6e73 100644 --- a/common/src/main/java/me/confuser/banmanager/common/configs/MessagesConfig.java +++ b/common/src/main/java/me/confuser/banmanager/common/configs/MessagesConfig.java @@ -2,10 +2,14 @@ import me.confuser.banmanager.common.CommonLogger; -import me.confuser.banmanager.common.util.Message; import java.io.File; +/** + * Retained for backwards compatibility. Message loading is now handled + * by {@link me.confuser.banmanager.common.BanManagerPlugin#setupConfigs()} + * via the {@link me.confuser.banmanager.common.util.MessageRegistry}. + */ public class MessagesConfig extends Config { public MessagesConfig(File dataFolder, CommonLogger logger) { @@ -13,11 +17,8 @@ public MessagesConfig(File dataFolder, CommonLogger logger) { } public void afterLoad() { - Message.load(this); } public void onSave() { - } - } diff --git a/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java b/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java index 24802a5a1..b3c37f7b9 100644 --- a/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java +++ b/common/src/main/java/me/confuser/banmanager/common/data/PlayerData.java @@ -32,6 +32,11 @@ public class PlayerData { @DatabaseField(columnDefinition = "BIGINT UNSIGNED NOT NULL") private long lastSeen = System.currentTimeMillis() / 1000L; + @DatabaseField(width = 16, columnDefinition = "VARCHAR(16)") + @Getter + @Setter + private String locale; + private UUID uuid = null; PlayerData() { diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java index e0e2d758e..c994082db 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonBanListener.java @@ -36,7 +36,7 @@ public void notifyOnBan(PlayerBanData data, boolean silent) { .set("reason", data.getReason()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; @@ -86,7 +86,7 @@ public void notifyOnBan(IpBanData data, boolean silent) { .set("players", playerNames.toString()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; @@ -126,7 +126,7 @@ public void notifyOnBan(IpRangeBanData data, boolean silent) { .set("reason", data.getReason()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; @@ -165,7 +165,7 @@ public void notifyOnBan(NameBanData data, boolean silent) { .set("reason", data.getReason()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonChatListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonChatListener.java index 289f72965..b0b49d7b1 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonChatListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonChatListener.java @@ -63,7 +63,7 @@ public boolean onPlayerChat(CommonPlayer player, CommonChatHandler handler, Stri .set("id", mute.getId()) .set("actor", mute.getActor().getName()); - plugin.getServer().broadcast(broadcast.toString(), "bm.notify.muted"); + plugin.getServer().broadcast(broadcast, "bm.notify.muted"); if (mute.isSoft()) { handler.handleSoftMute(); @@ -121,7 +121,7 @@ public boolean onIpChat(CommonPlayer player, InetAddress address, CommonChatHand .set("id", mute.getId()) .set("actor", mute.getActor().getName()); - plugin.getServer().broadcast(broadcast.toString(), "bm.notify.mutedip"); + plugin.getServer().broadcast(broadcast, "bm.notify.mutedip"); if (mute.isSoft()) { handler.handleSoftMute(); diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java index 21ce94a2c..c69f85655 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonJoinListener.java @@ -216,24 +216,25 @@ public void banCheck(UUID id, String name, IPAddress address, CommonJoinHandler return; } + String locale = data.getPlayer().getLocale(); String dateTimeFormat; Message message; if (data.getExpires() == 0) { message = Message.get("ban.player.disallowed"); - dateTimeFormat = Message.getString("ban.player.dateTimeFormat"); + dateTimeFormat = Message.getString("ban.player.dateTimeFormat", locale); } else { message = Message.get("tempban.player.disallowed"); - message.set("expires", DateUtils.getDifferenceFormat(data.getExpires())); + message.set("expires", DateUtils.getDifferenceFormat(data.getExpires(), locale)); - dateTimeFormat = Message.getString("tempban.player.dateTimeFormat"); + dateTimeFormat = Message.getString("tempban.player.dateTimeFormat", locale); } message.set("id", data.getId()); message.set("player", data.getPlayer().getName()); message.set("reason", data.getReason()); message.set("actor", data.getActor().getName()); - message.set("created", DateUtils.format(dateTimeFormat, data.getCreated())); + message.set("created", DateUtils.format(dateTimeFormat != null ? dateTimeFormat : "dd-MM-yyyy kk:mm:ss", data.getCreated())); handler.handlePlayerDeny(data.getPlayer(), message); handleJoinDeny(data.getPlayer(), data.getActor(), data.getReason()); @@ -265,6 +266,21 @@ public void onJoin(final CommonPlayer player) { UUID id = player.getUniqueId(); + if (plugin.getConfig().isPerPlayerLocale()) { + try { + PlayerData playerData = plugin.getPlayerStorage().queryForId(UUIDUtils.toBytes(id)); + if (playerData != null) { + String locale = player.getLocale(); + if (locale != null && !locale.equals(playerData.getLocale())) { + playerData.setLocale(locale); + plugin.getPlayerStorage().update(playerData); + } + } + } catch (SQLException e) { + plugin.getLogger().warning("Failed to update player locale", e); + } + } + PlayerMuteData mute = plugin.getPlayerMuteStorage().getMute(id); if (mute != null && mute.isOnlineOnly() && mute.isPaused()) { try { @@ -278,7 +294,7 @@ public void onJoin(final CommonPlayer player) { try { notesItr = plugin.getPlayerNoteStorage().getNotes(id); - ArrayList notes = new ArrayList<>(); + ArrayList notes = new ArrayList<>(); String dateTimeFormat = Message.getString("notes.dateTimeFormat"); while (notesItr != null && notesItr.hasNext()) { @@ -290,7 +306,7 @@ public void onJoin(final CommonPlayer player) { .set("id", note.getId()) .set("created", DateUtils.format(dateTimeFormat, note.getCreated())); - notes.add(noteMessage.toString()); + notes.add(noteMessage); } if (notes.size() != 0) { @@ -300,13 +316,12 @@ public void onJoin(final CommonPlayer player) { plugin.getServer().broadcastJSON(NotesCommand.notesAmountMessage(player.getName(), noteJoinMessage), "bm.notify.notes.joinAmount"); - String header = Message.get("notes.header") - .set("player", player.getName()) - .toString(); + Message header = Message.get("notes.header") + .set("player", player.getName()); plugin.getServer().broadcast(header, "bm.notify.notes.join"); - for (String message : notes) { + for (Message message : notes) { plugin.getServer().broadcast(message, "bm.notify.notes.join"); } @@ -464,7 +479,7 @@ public void onPlayerLogin(final CommonPlayer player, CommonJoinHandler handler) message.set("player", player.getName()); message.set("players", sb.toString()); - plugin.getServer().broadcast(message.toString(), "bm.notify.duplicateips"); + plugin.getServer().broadcast(message, "bm.notify.duplicateips"); }, Duration.ofSeconds(1)); plugin.getScheduler().runAsyncLater(() -> { @@ -498,7 +513,7 @@ public void onPlayerLogin(final CommonPlayer player, CommonJoinHandler handler) message.set("player", player.getName()); message.set("players", sb.toString()); - plugin.getServer().broadcast(message.toString(), "bm.notify.alts"); + plugin.getServer().broadcast(message, "bm.notify.alts"); }, Duration.ofSeconds(1)); } @@ -512,7 +527,7 @@ private void handleJoinDeny(PlayerData player, PlayerData actor, String reason) .set("reason", reason) .set("actor", actor.getName()); - plugin.getServer().broadcast(message.toString(), "bm.notify.denied.player"); + plugin.getServer().broadcast(message, "bm.notify.denied.player"); } private void handleJoinDeny(String ip, PlayerData actor, String reason) { @@ -524,7 +539,7 @@ private void handleJoinDeny(String ip, PlayerData actor, String reason) { .set("reason", reason) .set("actor", actor.getName()); - plugin.getServer().broadcast(message.toString(), "bm.notify.denied.ip"); + plugin.getServer().broadcast(message, "bm.notify.denied.ip"); } private void denyAlts(List duplicates, final UUID uuid) { @@ -552,7 +567,7 @@ private void denyAlts(List duplicates, final UUID uuid) { .set("id", ban.getId()) .set("actor", ban.getActor().getName()); - bukkitPlayer.kick(kickMessage.toString()); + bukkitPlayer.kick(kickMessage); }); } } @@ -593,7 +608,7 @@ private void punishAlts(List duplicates, UUID uuid) throws SQLExcept .set("id", newBan.getId()) .set("actor", newBan.getActor().getName()); - bukkitPlayer.kick(kickMessage.toString()); + bukkitPlayer.kick(kickMessage); }); } } else if (!plugin.getPlayerMuteStorage().isMuted(uuid)) { diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java index a26e9a137..c82ab19e7 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonMuteListener.java @@ -46,7 +46,7 @@ public void notifyOnMute(PlayerMuteData data, boolean silent) { .set("reason", data.getReason()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; @@ -97,7 +97,7 @@ public void notifyOnMute(IpMuteData data, boolean silent) { .set("players", playerNames.toString()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java index 43743a4eb..2771b6c8a 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonNoteListener.java @@ -27,7 +27,7 @@ public void notifyOnNote(PlayerNoteData data, boolean silent) { .set("message", data.getMessage()); if (!silent) { - plugin.getServer().broadcast(message.toString(), broadcastPermission); + plugin.getServer().broadcast(message, broadcastPermission); } else if (plugin.getPlayerStorage().getConsole().getUUID().equals(data.getActor().getUUID())) { plugin.getServer().getConsoleSender().sendMessage(message); return; diff --git a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java index f63a601fc..11c3db18f 100755 --- a/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java +++ b/common/src/main/java/me/confuser/banmanager/common/listeners/CommonReportListener.java @@ -24,7 +24,7 @@ public void notifyOnReport(PlayerReportData data) { .set("reason", data.getReason()) .set("id", data.getId()); - plugin.getServer().broadcast(message.toString(), "bm.notify.report"); + plugin.getServer().broadcast(message, "bm.notify.report"); // Check if the sender is online and does not have the // broadcastPermission diff --git a/common/src/main/java/me/confuser/banmanager/common/runnables/BanSync.java b/common/src/main/java/me/confuser/banmanager/common/runnables/BanSync.java index eef8825e7..fda566c42 100644 --- a/common/src/main/java/me/confuser/banmanager/common/runnables/BanSync.java +++ b/common/src/main/java/me/confuser/banmanager/common/runnables/BanSync.java @@ -66,7 +66,7 @@ private void newBans() { .set("reason", ban.getReason()) .set("actor", ban.getActor().getName()); - bukkitPlayer.kick(kickMessage.toString()); + bukkitPlayer.kick(kickMessage); }); } diff --git a/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalBanSync.java b/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalBanSync.java index 1f0735934..02dee8cf0 100644 --- a/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalBanSync.java +++ b/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalBanSync.java @@ -91,7 +91,7 @@ private void kickPlayer(PlayerBanData globalBan) { .set("reason", globalBan.getReason()) .set("actor", globalBan.getActor().getName()); - bukkitPlayer.kick(kickMessage.toString()); + bukkitPlayer.kick(kickMessage); }); } diff --git a/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalIpSync.java b/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalIpSync.java index 66d95681c..e79fd73a2 100644 --- a/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalIpSync.java +++ b/common/src/main/java/me/confuser/banmanager/common/runnables/GlobalIpSync.java @@ -77,7 +77,7 @@ private void kickPlayers(IpBanData globalBan) { for (CommonPlayer onlinePlayer : plugin.getServer().getOnlinePlayers()) { if (IPUtils.toIPAddress(onlinePlayer.getAddress()).equals(globalBan.getIp())) { - onlinePlayer.kick(kickMessage.toString()); + onlinePlayer.kick(kickMessage); } } }); diff --git a/common/src/main/java/me/confuser/banmanager/common/runnables/NameSync.java b/common/src/main/java/me/confuser/banmanager/common/runnables/NameSync.java index 2da425b74..545bc3bd0 100644 --- a/common/src/main/java/me/confuser/banmanager/common/runnables/NameSync.java +++ b/common/src/main/java/me/confuser/banmanager/common/runnables/NameSync.java @@ -62,7 +62,7 @@ private void newBans() { .set("reason", ban.getReason()) .set("actor", ban.getActor().getName()); - bukkitPlayer.kick(kickMessage.toString()); + bukkitPlayer.kick(kickMessage); }); } diff --git a/common/src/main/java/me/confuser/banmanager/common/util/DateUtils.java b/common/src/main/java/me/confuser/banmanager/common/util/DateUtils.java index f43c50608..7a3e32911 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/DateUtils.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/DateUtils.java @@ -29,8 +29,12 @@ public class DateUtils { .compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE); public static String formatDifference(long time) { + return formatDifference(time, null); + } + + public static String formatDifference(long time, String locale) { if (time == 0) { - return Message.getString("never"); + return locale != null ? Message.getString("never", locale) : Message.getString("never"); } StringBuilder diff = new StringBuilder(); @@ -69,24 +73,32 @@ public static String formatDifference(long time) { String key = timesString.get(i); if (duration > 1) key += "s"; - diff.append(Message.get("time." + key)); + if (locale != null) { + diff.append(Message.get("time." + key).resolve(locale)); + } else { + diff.append(Message.get("time." + key)); + } } - // Hack to avoid async error if ((field == Calendar.SECOND) && (duration == 59)) { - return formatDifference(time + 1); + return formatDifference(time + 1, locale); } } - return diff.length() == 0 ? Message.getString("time.now") : diff.toString(); + String nowStr = locale != null ? Message.getString("time.now", locale) : Message.getString("time.now"); + return diff.length() == 0 ? nowStr : diff.toString(); } public static String getDifferenceFormat(long timestamp) { return formatDifference(timestamp - (System.currentTimeMillis() / 1000L)); } + public static String getDifferenceFormat(long timestamp, String locale) { + return formatDifference(timestamp - (System.currentTimeMillis() / 1000L), locale); + } + // Copyright essentials, all credits to them for this. public static long parseDateDiff(String time, boolean future) throws Exception { // Support raw timestamps diff --git a/common/src/main/java/me/confuser/banmanager/common/util/Message.java b/common/src/main/java/me/confuser/banmanager/common/util/Message.java index 88b26b0cf..e8cccd492 100644 --- a/common/src/main/java/me/confuser/banmanager/common/util/Message.java +++ b/common/src/main/java/me/confuser/banmanager/common/util/Message.java @@ -1,6 +1,5 @@ package me.confuser.banmanager.common.util; -import lombok.EqualsAndHashCode; import lombok.Getter; import me.confuser.banmanager.common.BanManagerPlugin; import me.confuser.banmanager.common.CommonLogger; @@ -10,38 +9,41 @@ import me.confuser.banmanager.common.configs.Config; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; -import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; -@EqualsAndHashCode public class Message { - private static HashMap messages = new HashMap<>(10); - private static CommonLogger logger; + private static volatile MessageRegistry registry; + private static volatile CommonLogger logger; @Getter private String key; - private String message; + private final LinkedHashMap replacements = new LinkedHashMap<>(); public Message(String key) { this.key = key; - this.message = messages.get(key); - if (this.message == null) { - logger.warning("Missing " + key + " message"); - this.message = ""; + if (registry != null && registry.getMessage(key) == null) { + if (logger != null) logger.warning("Missing " + key + " message"); } } public Message(String key, String message) { - if (messages.containsKey(key)) { - logger.warning(key + " message already exists"); - return; - } - this.key = key; - this.message = message; - messages.put(key, message); + if (registry != null) { + if (registry.getMessage(key) != null) { + if (logger != null) logger.warning(key + " message already exists"); + return; + } + registry.putMessage(key, message); + } + } + + public static void init(MessageRegistry messageRegistry, CommonLogger commonLogger) { + registry = messageRegistry; + logger = commonLogger; } public static Message get(String key) { @@ -49,67 +51,90 @@ public static Message get(String key) { } public static String getString(String key) { - return messages.get(key); + if (registry == null) return null; + return registry.getMessage(key); } + public static String getString(String key, String locale) { + if (registry == null) return null; + return registry.getMessage(key, locale); + } + + /** + * @deprecated Use {@link #init(MessageRegistry, CommonLogger)} instead. + * Loads messages from the YAML config into the active registry for backwards compatibility. + */ + @Deprecated public static void load(YamlConfiguration config, CommonLogger commonLogger) { logger = commonLogger; - if (config.getConfigurationSection("messages") == null) { - commonLogger.warning("Messages section not found in messages.yml, keeping previous messages"); - return; - } - - HashMap newMessages = new HashMap<>(10); - for (String key : config.getConfigurationSection("messages").getKeys(true)) { - String value = config.getString("messages." + key); - if (value != null) { - newMessages.put(key, value.replace("\\n", "\n").replaceAll("(?<=\\n)(?=\\n)", " ")); + if (registry != null && config.getConfigurationSection("messages") != null) { + for (String key : config.getConfigurationSection("messages").getKeys(true)) { + String value = config.getString("messages." + key); + if (value != null) { + registry.putMessage(key, value.replace("\\n", "\n").replaceAll("(?<=\\n)(?=\\n)", " ")); + } } } - - if (!newMessages.isEmpty()) { - messages.clear(); - messages.putAll(newMessages); - } else { - commonLogger.warning("No messages loaded from messages.yml, keeping previous messages"); - } } + /** + * @deprecated Use {@link #init(MessageRegistry, CommonLogger)} instead. + */ + @Deprecated public static void load(Config config) { load(config.conf, config.getLogger()); } public Message replace(CharSequence oldChar, CharSequence newChar) { - message = this.message.replace(oldChar, newChar); - + replacements.put("__replace__" + replacements.size(), new String[]{oldChar.toString(), newChar.toString()}); return this; } public Message set(String token, String replace) { - return replace("[" + token + "]", replace); + replacements.put(token, new String[]{"[" + token + "]", replace}); + return this; } public Message set(String token, Integer replace) { - return replace("[" + token + "]", replace.toString()); + return set(token, replace.toString()); } public Message set(String token, Double replace) { - return replace("[" + token + "]", replace.toString()); + return set(token, replace.toString()); } public Message set(String token, Long replace) { - return replace("[" + token + "]", replace.toString()); + return set(token, replace.toString()); } public Message set(String token, Float replace) { - return replace("[" + token + "]", replace.toString()); + return set(token, replace.toString()); + } + + public String resolve(String locale) { + if (registry == null) return ""; + + String template = registry.getMessage(key, locale); + if (template == null) return ""; + + return applyReplacements(template); + } + + public String resolveFor(CommonPlayer player) { + if (player == null) return resolve(getDefaultLocale()); + + BanManagerPlugin plugin = BanManagerPlugin.getInstance(); + if (plugin != null && plugin.getConfig() != null && plugin.getConfig().isPerPlayerLocale()) { + return resolve(player.getLocale()); + } + return resolve(getDefaultLocale()); } public boolean sendTo(CommonSender sender) { if (sender == null) return false; - sender.sendMessage(message); + sender.sendMessage(resolve(getDefaultLocale())); return true; } @@ -118,10 +143,10 @@ public boolean sendTo(CommonPlayer player) { if (player == null) return false; if (!player.isOnline()) return false; - String resolved = message; + String resolved = resolveFor(player); PlaceholderResolver resolver = BanManagerPlugin.getInstance().getPlaceholderResolver(); if (resolver != null) { - resolved = resolver.resolve(player, message); + resolved = resolver.resolve(player, resolved); } player.sendMessage(resolved); @@ -135,6 +160,20 @@ public static boolean isJSONMessage(String message) { @Override public String toString() { - return message; + return resolve(getDefaultLocale()); + } + + private String applyReplacements(String template) { + String result = template; + for (Map.Entry entry : replacements.entrySet()) { + String[] pair = entry.getValue(); + result = result.replace(pair[0], pair[1]); + } + return result; + } + + private static String getDefaultLocale() { + if (registry == null) return "en"; + return registry.getDefaultLocale(); } } diff --git a/common/src/main/java/me/confuser/banmanager/common/util/MessageRegistry.java b/common/src/main/java/me/confuser/banmanager/common/util/MessageRegistry.java new file mode 100644 index 000000000..341946a65 --- /dev/null +++ b/common/src/main/java/me/confuser/banmanager/common/util/MessageRegistry.java @@ -0,0 +1,139 @@ +package me.confuser.banmanager.common.util; + +import java.util.*; + +public class MessageRegistry { + + private static final class Snapshot { + final String defaultLocale; + final Map> locales; + + Snapshot(String defaultLocale, Map> locales) { + this.defaultLocale = defaultLocale; + this.locales = locales; + } + } + + private volatile Snapshot snapshot; + + public MessageRegistry(String defaultLocale) { + this.snapshot = new Snapshot(normaliseLocale(defaultLocale), new HashMap<>()); + } + + public static String normaliseLocale(String locale) { + if (locale == null || locale.isEmpty()) return "en"; + return locale.toLowerCase(Locale.ROOT).replace('-', '_'); + } + + public String getDefaultLocale() { + return snapshot.defaultLocale; + } + + public void loadLocale(String locale, Map messages) { + String normalised = normaliseLocale(locale); + Snapshot s = this.snapshot; + Map> copy = new HashMap<>(s.locales); + copy.put(normalised, Collections.unmodifiableMap(new HashMap<>(messages))); + this.snapshot = new Snapshot(s.defaultLocale, copy); + } + + public String getMessage(String key, String locale) { + String normalised = normaliseLocale(locale); + Snapshot s = this.snapshot; + + String value = getFromLocale(s.locales, key, normalised); + if (value != null) return value; + + int underscore = normalised.indexOf('_'); + if (underscore > 0) { + String baseLanguage = normalised.substring(0, underscore); + value = getFromLocale(s.locales, key, baseLanguage); + if (value != null) return value; + } + + if (!normalised.equals(s.defaultLocale)) { + value = getFromLocale(s.locales, key, s.defaultLocale); + if (value != null) return value; + } + + return null; + } + + public String getMessage(String key) { + return getMessage(key, snapshot.defaultLocale); + } + + public void putMessage(String key, String message) { + putMessage(key, message, snapshot.defaultLocale); + } + + public void putMessage(String key, String message, String locale) { + String normalised = normaliseLocale(locale); + Snapshot s = this.snapshot; + Map> copy = new HashMap<>(s.locales); + Map localeMessages = copy.get(normalised); + + if (localeMessages == null) { + localeMessages = new HashMap<>(); + } else { + localeMessages = new HashMap<>(localeMessages); + } + + localeMessages.put(key, message); + copy.put(normalised, Collections.unmodifiableMap(localeMessages)); + this.snapshot = new Snapshot(s.defaultLocale, copy); + } + + public Set getAvailableLocales() { + return Collections.unmodifiableSet(new HashSet<>(snapshot.locales.keySet())); + } + + public Set getKeys(String locale) { + Map localeMessages = snapshot.locales.get(normaliseLocale(locale)); + if (localeMessages == null) return Collections.emptySet(); + return Collections.unmodifiableSet(localeMessages.keySet()); + } + + public Map getMessages(String locale) { + Map localeMessages = snapshot.locales.get(normaliseLocale(locale)); + if (localeMessages == null) return Collections.emptyMap(); + return localeMessages; + } + + public boolean hasAnyMessages() { + Snapshot s = this.snapshot; + for (Map msgs : s.locales.values()) { + if (!msgs.isEmpty()) return true; + } + return false; + } + + public int getMissingKeyCount(String locale) { + Snapshot s = this.snapshot; + Set defaultKeys = getKeysFromSnapshot(s, s.defaultLocale); + Set localeKeys = getKeysFromSnapshot(s, normaliseLocale(locale)); + int missing = 0; + + for (String key : defaultKeys) { + if (!localeKeys.contains(key)) missing++; + } + + return missing; + } + + public void atomicSwap(MessageRegistry newRegistry) { + this.snapshot = newRegistry.snapshot; + } + + private static Set getKeysFromSnapshot(Snapshot s, String locale) { + Map messages = s.locales.get(locale); + if (messages == null) return Collections.emptySet(); + return messages.keySet(); + } + + private static String getFromLocale(Map> locales, String key, String locale) { + Map messages = locales.get(locale); + if (messages == null) return null; + return messages.get(key); + } +} diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 50f49665d..a1de75a53 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -1,3 +1,7 @@ +locale: + default: en + perPlayer: true + debug: false databases: # Local Database is always required. If not enabled, plugin will disable on startup. diff --git a/common/src/main/resources/db/local/V2__add_player_locale.sql b/common/src/main/resources/db/local/V2__add_player_locale.sql new file mode 100644 index 000000000..f725c3df5 --- /dev/null +++ b/common/src/main/resources/db/local/V2__add_player_locale.sql @@ -0,0 +1 @@ +ALTER TABLE ${players} ADD COLUMN `locale` VARCHAR(16) NULL; diff --git a/common/src/main/resources/db/local/migrations.list b/common/src/main/resources/db/local/migrations.list index a43ed9c9a..43cde01af 100644 --- a/common/src/main/resources/db/local/migrations.list +++ b/common/src/main/resources/db/local/migrations.list @@ -1 +1,2 @@ V1__baseline.sql lenient +V2__add_player_locale.sql lenient diff --git a/common/src/main/resources/messages/messages_en.yml b/common/src/main/resources/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/common/src/main/resources/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/common/src/test/java/me/confuser/banmanager/common/BasePluginTest.java b/common/src/test/java/me/confuser/banmanager/common/BasePluginTest.java index 12f50f31c..dd9aa68fc 100644 --- a/common/src/test/java/me/confuser/banmanager/common/BasePluginTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/BasePluginTest.java @@ -58,6 +58,19 @@ public static PluginInfo setupConfigs(TemporaryFolder folder) { } } + File messagesDir = new File(folder.getRoot(), "messages"); + messagesDir.mkdirs(); + try (InputStream in = BasePluginTest.class.getClassLoader().getResource("messages/messages_en.yml").openStream(); + OutputStream out = new FileOutputStream(new File(messagesDir, "messages_en.yml"))) { + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } catch (IOException e) { + e.printStackTrace(); + } + // Load plugin.yml PluginInfo pluginInfo = new PluginInfo(); try (InputStream in = BasePluginTest.class.getClassLoader().getResource("plugin.yml").openStream(); diff --git a/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java b/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java index e978bbe47..a8125b60d 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestPlayer.java @@ -12,6 +12,7 @@ public class TestPlayer implements CommonPlayer { private final UUID uuid; private final String name; private final boolean onlineMode; + private String locale = "en"; public TestPlayer(UUID uuid, String name, boolean onlineMode) { this.uuid = uuid; @@ -19,6 +20,15 @@ public TestPlayer(UUID uuid, String name, boolean onlineMode) { this.onlineMode = onlineMode; } + public TestPlayer(UUID uuid, String name, boolean onlineMode, String locale) { + this(uuid, name, onlineMode); + this.locale = locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + @Override public void kick(String message) { } @@ -93,4 +103,9 @@ public boolean teleport(CommonWorld world, double x, double y, double z, float p public boolean canSee(CommonPlayer player) { return true; } + + @Override + public String getLocale() { + return locale; + } } diff --git a/common/src/test/java/me/confuser/banmanager/common/TestServer.java b/common/src/test/java/me/confuser/banmanager/common/TestServer.java index f90b27876..b3588c106 100644 --- a/common/src/test/java/me/confuser/banmanager/common/TestServer.java +++ b/common/src/test/java/me/confuser/banmanager/common/TestServer.java @@ -4,6 +4,7 @@ import me.confuser.banmanager.common.commands.CommonSender; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.kyori.text.TextComponent; +import me.confuser.banmanager.common.util.Message; import me.confuser.banmanager.common.util.UUIDUtils; import java.sql.SQLException; @@ -104,6 +105,10 @@ public CommonPlayer[] getOnlinePlayers() { public void broadcast(String message, String permission) { } + @Override + public void broadcast(Message message, String permission) { + } + @Override public void broadcastJSON(TextComponent message, String permission) { } diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/KickAllCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/KickAllCommandTest.java index 009c408ad..5283d3700 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/KickAllCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/KickAllCommandTest.java @@ -3,9 +3,12 @@ import me.confuser.banmanager.common.BasePluginDbTest; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.util.Message; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class KickAllCommandTest extends BasePluginDbTest { @@ -46,7 +49,10 @@ public void shouldFailIfExempt() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer, never()).kick("&6You have been kicked"); - verify(server).broadcast("All players have been kicked by Console for &4test", "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console for &4test", msgCaptor.getValue().toString()); } @Test @@ -62,7 +68,10 @@ public void shouldKickPlayerWithoutAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server).broadcast("All players have been kicked by Console", "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console", msgCaptor.getValue().toString()); } @Test @@ -78,7 +87,10 @@ public void shouldKickPlayerWithAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked for &4" + args[0]); - verify(server).broadcast("All players have been kicked by Console for &4" + args[0], "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console for &4" + args[0], msgCaptor.getValue().toString()); } @Test @@ -94,7 +106,7 @@ public void shouldKickPlayerWithoutAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server, never()).broadcast("All players have been kicked by Console", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } @Test @@ -110,6 +122,6 @@ public void shouldKickPlayerWithAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked for &4test reason"); - verify(server, never()).broadcast("All players have been kicked by Console for &4test reason", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/KickCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/KickCommandTest.java index 31c415ec6..97a0e3b2c 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/KickCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/KickCommandTest.java @@ -3,8 +3,10 @@ import me.confuser.banmanager.common.BasePluginDbTest; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.util.Message; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -87,7 +89,10 @@ public void shouldKickPlayerWithoutAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server).broadcast("&6" + args[0] + " has been kicked by Console", "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("&6" + args[0] + " has been kicked by Console", msgCaptor.getValue().toString()); } @Test @@ -103,7 +108,10 @@ public void shouldKickPlayerWithAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args))); verify(commonPlayer).kick("&6You have been kicked for &4" + args[1]); - verify(server).broadcast("&6" + args[0] + " has been kicked by Console for &4" + args[1], "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("&6" + args[0] + " has been kicked by Console for &4" + args[1], msgCaptor.getValue().toString()); } @Test @@ -119,7 +127,7 @@ public void shouldKickPlayerWithoutAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server, never()).broadcast("&6" + args[0] + " has been kicked by Console", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } @Test @@ -135,6 +143,6 @@ public void shouldKickPlayerWithAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args))); verify(commonPlayer).kick("&6You have been kicked for &4test reason"); - verify(server, never()).broadcast("&6" + args[0] + " has been kicked by Console for &4test reason", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/commands/LoglessKickAllCommandTest.java b/common/src/test/java/me/confuser/banmanager/common/commands/LoglessKickAllCommandTest.java index 0fc23f900..9c09e242e 100644 --- a/common/src/test/java/me/confuser/banmanager/common/commands/LoglessKickAllCommandTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/commands/LoglessKickAllCommandTest.java @@ -3,9 +3,12 @@ import me.confuser.banmanager.common.BasePluginDbTest; import me.confuser.banmanager.common.CommonPlayer; import me.confuser.banmanager.common.data.PlayerData; +import me.confuser.banmanager.common.util.Message; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class LoglessKickAllCommandTest extends BasePluginDbTest { @@ -46,7 +49,10 @@ public void shouldFailIfExempt() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer, never()).kick("&6You have been kicked"); - verify(server).broadcast("All players have been kicked by Console for &4test", "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console for &4test", msgCaptor.getValue().toString()); } @Test @@ -62,7 +68,10 @@ public void shouldKickPlayerWithoutAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server).broadcast("All players have been kicked by Console", "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console", msgCaptor.getValue().toString()); } @Test @@ -78,7 +87,10 @@ public void shouldKickPlayerWithAReason() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked for &4" + args[0]); - verify(server).broadcast("All players have been kicked by Console for &4" + args[0], "bm.notify.kick"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(msgCaptor.capture(), eq("bm.notify.kick")); + assertEquals("All players have been kicked by Console for &4" + args[0], msgCaptor.getValue().toString()); } @Test @@ -94,7 +106,7 @@ public void shouldKickPlayerWithoutAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked"); - verify(server, never()).broadcast("All players have been kicked by Console", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } @Test @@ -110,6 +122,6 @@ public void shouldKickPlayerWithAReasonSilently() { assert (cmd.onCommand(sender, new CommandParser(plugin, args, 0))); verify(commonPlayer).kick("&6You have been kicked for &4test reason"); - verify(server, never()).broadcast("All players have been kicked by Console for &4test reason", "bm.notify.kick"); + verify(server, never()).broadcast(any(Message.class), eq("bm.notify.kick")); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/configs/ConfigReloadTest.java b/common/src/test/java/me/confuser/banmanager/common/configs/ConfigReloadTest.java index a517027d7..3f42f85e8 100644 --- a/common/src/test/java/me/confuser/banmanager/common/configs/ConfigReloadTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/configs/ConfigReloadTest.java @@ -97,20 +97,21 @@ public void setupConfigsReplacesConfigOnSuccessfulReload() throws IOException { @Test public void messagesArePreservedWhenConfigFails() throws IOException { - // Get a message that should exist String originalMessage = Message.getString("configReloaded"); assertNotNull("configReloaded message should exist", originalMessage); - // Corrupt the messages.yml file + // Corrupt both messages.yml and messages_en.yml File messagesFile = new File(temporaryFolder.getRoot(), "messages.yml"); try (FileWriter writer = new FileWriter(messagesFile)) { writer.write("invalid: yaml: [unclosed"); } + File messagesEnFile = new File(temporaryFolder.getRoot(), "messages/messages_en.yml"); + try (FileWriter writer = new FileWriter(messagesEnFile)) { + writer.write("invalid: yaml: [unclosed"); + } - // Reload configs plugin.setupConfigs(); - // Messages should still be available String afterReloadMessage = Message.getString("configReloaded"); assertEquals("Messages should be preserved after failed reload", originalMessage, afterReloadMessage); } diff --git a/common/src/test/java/me/confuser/banmanager/common/configs/MessagesConfigTest.java b/common/src/test/java/me/confuser/banmanager/common/configs/MessagesConfigTest.java index 283cbf5e8..839a76f3d 100644 --- a/common/src/test/java/me/confuser/banmanager/common/configs/MessagesConfigTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/configs/MessagesConfigTest.java @@ -4,6 +4,7 @@ import me.confuser.banmanager.common.TestLogger; import me.confuser.banmanager.common.configuration.file.YamlConfiguration; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import org.junit.Test; import java.io.StringReader; @@ -15,51 +16,49 @@ public class MessagesConfigTest extends BasePluginTest { @Test public void isValid() { - // @TODO test tokens for all messages assertEquals("&6Currently banned for &4abc&6 by def at 8th July which expires in 1d", Message .get("info.ban.temporary").set("reason", "abc").set("actor", "def").set("created", "8th July").set("expires", "1d").toString()); } - @Test - public void doubleNewlineProducesNonEmptyLine() { - String yaml = "messages:\n test:\n greeting: 'Hello\\n\\nWorld'"; + private void loadTestMessages(String yaml) { YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(yaml)); - Message.load(config, new TestLogger()); + MessageRegistry registry = new MessageRegistry("en"); - String result = Message.get("test.greeting").toString(); + for (String key : config.getConfigurationSection("messages").getKeys(true)) { + String value = config.getString("messages." + key); + if (value != null) { + registry.putMessage(key, value.replace("\\n", "\n").replaceAll("(?<=\\n)(?=\\n)", " ")); + } + } + Message.init(registry, new TestLogger()); + } + + @Test + public void doubleNewlineProducesNonEmptyLine() { + loadTestMessages("messages:\n test:\n greeting: 'Hello\\n\\nWorld'"); + String result = Message.get("test.greeting").toString(); assertEquals("Hello\n \nWorld", result); } @Test public void tripleNewlineProducesNonEmptyLines() { - String yaml = "messages:\n test:\n greeting: 'Hello\\n\\n\\nWorld'"; - YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(yaml)); - Message.load(config, new TestLogger()); - + loadTestMessages("messages:\n test:\n greeting: 'Hello\\n\\n\\nWorld'"); String result = Message.get("test.greeting").toString(); - assertEquals("Hello\n \n \nWorld", result); } @Test public void singleNewlineIsUnchanged() { - String yaml = "messages:\n test:\n greeting: 'Hello\\nWorld'"; - YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(yaml)); - Message.load(config, new TestLogger()); - + loadTestMessages("messages:\n test:\n greeting: 'Hello\\nWorld'"); String result = Message.get("test.greeting").toString(); - assertEquals("Hello\nWorld", result); } @Test public void noConsecutiveEmptyLinesInLoadedMessages() { - String yaml = "messages:\n test:\n msg: 'Line1\\n\\nLine2\\n\\n\\nLine3'"; - YamlConfiguration config = YamlConfiguration.loadConfiguration(new StringReader(yaml)); - Message.load(config, new TestLogger()); - + loadTestMessages("messages:\n test:\n msg: 'Line1\\n\\nLine2\\n\\n\\nLine3'"); String result = Message.get("test.msg").toString(); for (String line : result.split("\n", -1)) { diff --git a/common/src/test/java/me/confuser/banmanager/common/listeners/CommonJoinListenerTest.java b/common/src/test/java/me/confuser/banmanager/common/listeners/CommonJoinListenerTest.java index ab22bbe51..608e20fb8 100644 --- a/common/src/test/java/me/confuser/banmanager/common/listeners/CommonJoinListenerTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/listeners/CommonJoinListenerTest.java @@ -349,6 +349,9 @@ public boolean teleport(CommonWorld world, double x, double y, double z, float p @Override public boolean canSee(CommonPlayer player) { return true; } + + @Override + public String getLocale() { return "en"; } }; } } diff --git a/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java b/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java index 2007faa01..7f03dee52 100755 --- a/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/listeners/NoteListenerTest.java @@ -6,7 +6,9 @@ import me.confuser.banmanager.common.data.PlayerNoteData; import me.confuser.banmanager.common.util.Message; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class NoteListenerTest extends BasePluginDbTest { @@ -18,9 +20,9 @@ public void shouldBroadcast() { CommonSender sender = plugin.getServer().getConsoleSender(); PlayerNoteData data = new PlayerNoteData(player, sender.getData(), "test"); CommonPlayer testPlayer = spy(new TestPlayer(sender.getData().getUUID(), sender.getName(), false)); - Message message = Message.get("notes.notify"); + Message expected = Message.get("notes.notify"); - message.set("player", data.getPlayer().getName()) + expected.set("player", data.getPlayer().getName()) .set("playerId", data.getPlayer().getUUID().toString()) .set("actor", data.getActor().getName()) .set("message", data.getMessage()); @@ -32,7 +34,12 @@ public void shouldBroadcast() { CommonNoteListener listener = new CommonNoteListener(plugin); listener.notifyOnNote(data); - verify(server).broadcast(message.toString(), "bm.notify.notes"); - verify(testPlayer).sendMessage(message); + ArgumentCaptor broadcastCaptor = ArgumentCaptor.forClass(Message.class); + verify(server).broadcast(broadcastCaptor.capture(), eq("bm.notify.notes")); + assertEquals(expected.toString(), broadcastCaptor.getValue().toString()); + + ArgumentCaptor playerCaptor = ArgumentCaptor.forClass(Message.class); + verify(testPlayer).sendMessage(playerCaptor.capture()); + assertEquals(expected.toString(), playerCaptor.getValue().toString()); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/storage/PlayerStorageTest.java b/common/src/test/java/me/confuser/banmanager/common/storage/PlayerStorageTest.java index 6b72639c4..350fc450f 100644 --- a/common/src/test/java/me/confuser/banmanager/common/storage/PlayerStorageTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/storage/PlayerStorageTest.java @@ -40,6 +40,51 @@ public void shouldUpsertPlayerData() throws SQLException { assertNotNull(playerStorage.getAutoCompleteTree().getValueForExactKey("Name 2")); } + @Test + public void shouldPersistLocale() throws SQLException { + PlayerStorage playerStorage = plugin.getPlayerStorage(); + + UUID uuid = UUID.randomUUID(); + PlayerData data = new PlayerData(uuid, "LocalePlayer"); + data.setLocale("de"); + + playerStorage.upsert(data); + + PlayerData retrieved = playerStorage.retrieve("LocalePlayer", false); + assertNotNull(retrieved); + assertEquals("de", retrieved.getLocale()); + } + + @Test + public void shouldUpdateLocale() throws SQLException { + PlayerStorage playerStorage = plugin.getPlayerStorage(); + + UUID uuid = UUID.randomUUID(); + PlayerData data = new PlayerData(uuid, "LocaleUpdate"); + data.setLocale("en"); + playerStorage.upsert(data); + + data.setLocale("fr"); + playerStorage.upsert(data); + + PlayerData retrieved = playerStorage.retrieve("LocaleUpdate", false); + assertNotNull(retrieved); + assertEquals("fr", retrieved.getLocale()); + } + + @Test + public void shouldHandleNullLocale() throws SQLException { + PlayerStorage playerStorage = plugin.getPlayerStorage(); + + UUID uuid = UUID.randomUUID(); + PlayerData data = new PlayerData(uuid, "NullLocale"); + playerStorage.upsert(data); + + PlayerData retrieved = playerStorage.retrieve("NullLocale", false); + assertNotNull(retrieved); + assertNull(retrieved.getLocale()); + } + @Test public void shouldRetrievePlayerData() throws SQLException { PlayerStorage playerStorage = plugin.getPlayerStorage(); diff --git a/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java index 62aa4729e..466deb1b5 100644 --- a/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java +++ b/common/src/test/java/me/confuser/banmanager/common/storage/migration/MigrationIntegrationTest.java @@ -50,7 +50,7 @@ public void freshInstall_marksLatestVersion() throws Exception { for (SchemaVersion sv : versions) { if ("local".equals(sv.getScope())) { hasLocal = true; - assertEquals("Fresh install should mark at latest version", 1, sv.getVersion()); + assertEquals("Fresh install should mark at latest version", 2, sv.getVersion()); assertTrue("Description should indicate fresh install", sv.getDescription().contains("fresh install")); } } diff --git a/common/src/test/java/me/confuser/banmanager/common/util/LocaleNormalisationTest.java b/common/src/test/java/me/confuser/banmanager/common/util/LocaleNormalisationTest.java new file mode 100644 index 000000000..e88a7a93f --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/util/LocaleNormalisationTest.java @@ -0,0 +1,48 @@ +package me.confuser.banmanager.common.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class LocaleNormalisationTest { + + @Test + public void enGBBecomesEnGb() { + assertEquals("en_gb", MessageRegistry.normaliseLocale("en_GB")); + } + + @Test + public void zhTWBecomesZhTw() { + assertEquals("zh_tw", MessageRegistry.normaliseLocale("zh-TW")); + } + + @Test + public void uppercaseBecomesLowercase() { + assertEquals("en", MessageRegistry.normaliseLocale("EN")); + } + + @Test + public void ptBRBecomesPtBr() { + assertEquals("pt_br", MessageRegistry.normaliseLocale("pt_BR")); + } + + @Test + public void hyphenIsReplacedWithUnderscore() { + assertEquals("en_gb", MessageRegistry.normaliseLocale("en-gb")); + } + + @Test + public void alreadyNormalisedIsUnchanged() { + assertEquals("en", MessageRegistry.normaliseLocale("en")); + } + + @Test + public void nullReturnsEn() { + assertEquals("en", MessageRegistry.normaliseLocale(null)); + } + + @Test + public void emptyStringReturnsEn() { + assertEquals("en", MessageRegistry.normaliseLocale("")); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java new file mode 100644 index 000000000..a50be3c40 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessageDeferredTest.java @@ -0,0 +1,120 @@ +package me.confuser.banmanager.common.util; + +import me.confuser.banmanager.common.TestLogger; +import me.confuser.banmanager.common.TestPlayer; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.*; + +public class MessageDeferredTest { + + @Before + public void setUp() { + MessageRegistry registry = new MessageRegistry("en"); + + Map en = new HashMap<>(); + en.put("ban.kick", "You are banned by [actor] for [reason]"); + en.put("greeting", "Hello [player]"); + registry.loadLocale("en", en); + + Map de = new HashMap<>(); + de.put("ban.kick", "Du wurdest von [actor] gebannt: [reason]"); + de.put("greeting", "Hallo [player]"); + registry.loadLocale("de", de); + + Message.init(registry, new TestLogger()); + } + + @Test + public void resolveWithDefaultLocale() { + String result = Message.get("ban.kick") + .set("actor", "Admin") + .set("reason", "griefing") + .resolve("en"); + + assertEquals("You are banned by Admin for griefing", result); + } + + @Test + public void resolveWithSpecificLocale() { + String result = Message.get("ban.kick") + .set("actor", "Admin") + .set("reason", "griefing") + .resolve("de"); + + assertEquals("Du wurdest von Admin gebannt: griefing", result); + } + + @Test + public void toStringUsesDefaultLocale() { + String result = Message.get("greeting") + .set("player", "Steve") + .toString(); + + assertEquals("Hello Steve", result); + } + + @Test + public void resolveWithPlayerLocale() { + TestPlayer player = new TestPlayer(UUID.randomUUID(), "Steve", true, "de"); + String result = Message.get("greeting") + .set("player", "Steve") + .resolve(player.getLocale()); + + assertEquals("Hallo Steve", result); + } + + @Test + public void resolveForFallsBackWithoutPlugin() { + TestPlayer player = new TestPlayer(UUID.randomUUID(), "Steve", true, "de"); + String result = Message.get("greeting") + .set("player", "Steve") + .resolveFor(player); + + assertEquals("Hello Steve", result); + } + + @Test + public void tokenReplacementOrderPreserved() { + MessageRegistry registry = new MessageRegistry("en"); + Map en = new HashMap<>(); + en.put("test.order", "[a] [b] [a]"); + registry.loadLocale("en", en); + Message.init(registry, new TestLogger()); + + String result = Message.get("test.order") + .set("a", "X") + .set("b", "Y") + .toString(); + + assertEquals("X Y X", result); + } + + @Test + public void missingKeyReturnsEmptyString() { + String result = Message.get("nonexistent.key").toString(); + assertEquals("", result); + } + + @Test + public void dynamicRegistrationWritesToDefaultLocale() { + new Message("custom.key", "Custom message"); + + assertEquals("Custom message", Message.getString("custom.key")); + } + + @Test + public void replaceMethodWorks() { + String result = Message.get("greeting") + .set("player", "Steve") + .replace("Hello", "Hey") + .toString(); + + assertEquals("Hey Steve", result); + } +} diff --git a/common/src/test/java/me/confuser/banmanager/common/util/MessageRegistryTest.java b/common/src/test/java/me/confuser/banmanager/common/util/MessageRegistryTest.java new file mode 100644 index 000000000..80a123c00 --- /dev/null +++ b/common/src/test/java/me/confuser/banmanager/common/util/MessageRegistryTest.java @@ -0,0 +1,135 @@ +package me.confuser.banmanager.common.util; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +public class MessageRegistryTest { + + private MessageRegistry registry; + + @Before + public void setUp() { + registry = new MessageRegistry("en"); + + Map en = new HashMap<>(); + en.put("greeting", "Hello"); + en.put("farewell", "Goodbye"); + en.put("only.in.english", "English only"); + registry.loadLocale("en", en); + + Map zh = new HashMap<>(); + zh.put("greeting", "你好"); + zh.put("farewell", "再见"); + registry.loadLocale("zh", zh); + + Map zhTw = new HashMap<>(); + zhTw.put("greeting", "你好 (繁體)"); + registry.loadLocale("zh_tw", zhTw); + } + + @Test + public void cascadingFallbackExactLocale() { + assertEquals("你好 (繁體)", registry.getMessage("greeting", "zh_tw")); + } + + @Test + public void cascadingFallbackToBaseLanguage() { + assertEquals("再见", registry.getMessage("farewell", "zh_tw")); + } + + @Test + public void cascadingFallbackToDefault() { + assertEquals("English only", registry.getMessage("only.in.english", "zh_tw")); + } + + @Test + public void missingKeyFallbackToDefault() { + assertEquals("English only", registry.getMessage("only.in.english", "de")); + } + + @Test + public void unknownLocaleReturnsDefault() { + assertEquals("Hello", registry.getMessage("greeting", "fr")); + } + + @Test + public void defaultLocaleReturnsDirectly() { + assertEquals("Hello", registry.getMessage("greeting")); + } + + @Test + public void missingKeyReturnsNull() { + assertNull(registry.getMessage("nonexistent", "en")); + } + + @Test + public void availableLocales() { + Set locales = registry.getAvailableLocales(); + assertTrue(locales.contains("en")); + assertTrue(locales.contains("zh")); + assertTrue(locales.contains("zh_tw")); + assertEquals(3, locales.size()); + } + + @Test + public void missingKeyCount() { + assertEquals(1, registry.getMissingKeyCount("zh")); + assertEquals(2, registry.getMissingKeyCount("zh_tw")); + assertEquals(0, registry.getMissingKeyCount("en")); + } + + @Test + public void atomicSwapReplacesContent() { + MessageRegistry newRegistry = new MessageRegistry("de"); + Map de = new HashMap<>(); + de.put("greeting", "Hallo"); + newRegistry.loadLocale("de", de); + + registry.atomicSwap(newRegistry); + + assertEquals("de", registry.getDefaultLocale()); + assertEquals("Hallo", registry.getMessage("greeting")); + assertNull(registry.getMessage("farewell")); + } + + @Test + public void putMessageUpdatesExistingLocale() { + registry.putMessage("greeting", "Hi", "en"); + assertEquals("Hi", registry.getMessage("greeting", "en")); + } + + @Test + public void putMessageCreatesNewLocale() { + registry.putMessage("greeting", "Bonjour", "fr"); + assertEquals("Bonjour", registry.getMessage("greeting", "fr")); + assertTrue(registry.getAvailableLocales().contains("fr")); + } + + @Test + public void hasAnyMessagesReturnsTrueWhenLoaded() { + assertTrue(registry.hasAnyMessages()); + } + + @Test + public void hasAnyMessagesReturnsFalseWhenEmpty() { + MessageRegistry empty = new MessageRegistry("en"); + assertFalse(empty.hasAnyMessages()); + } + + @Test + public void defaultLocaleMismatchStillResolvesViaFallback() { + MessageRegistry reg = new MessageRegistry("en_us"); + Map en = new HashMap<>(); + en.put("test", "Hello"); + reg.loadLocale("en", en); + + assertEquals("Hello", reg.getMessage("test", "en_us")); + assertTrue(reg.hasAnyMessages()); + } +} diff --git a/e2e/platforms/bukkit/configs/config.yml b/e2e/platforms/bukkit/configs/config.yml index c9a784112..b8ac0e3a3 100644 --- a/e2e/platforms/bukkit/configs/config.yml +++ b/e2e/platforms/bukkit/configs/config.yml @@ -1,3 +1,7 @@ +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/bukkit/configs/messages/messages_de.yml b/e2e/platforms/bukkit/configs/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/bukkit/configs/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/bukkit/configs/messages/messages_en.yml b/e2e/platforms/bukkit/configs/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/bukkit/configs/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/platforms/bungee/configs/banmanager/config.yml b/e2e/platforms/bungee/configs/banmanager/config.yml index 8581b02e3..4c59ef67e 100644 --- a/e2e/platforms/bungee/configs/banmanager/config.yml +++ b/e2e/platforms/bungee/configs/banmanager/config.yml @@ -1,5 +1,9 @@ # # Aliases will be found and blocked automatically, e.g. msg will block tell +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/bungee/configs/banmanager/messages/messages_de.yml b/e2e/platforms/bungee/configs/banmanager/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/bungee/configs/banmanager/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/bungee/configs/banmanager/messages/messages_en.yml b/e2e/platforms/bungee/configs/banmanager/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/bungee/configs/banmanager/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/platforms/fabric/configs/config.yml b/e2e/platforms/fabric/configs/config.yml index c9a784112..b8ac0e3a3 100644 --- a/e2e/platforms/fabric/configs/config.yml +++ b/e2e/platforms/fabric/configs/config.yml @@ -1,3 +1,7 @@ +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/fabric/configs/messages/messages_de.yml b/e2e/platforms/fabric/configs/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/fabric/configs/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/fabric/configs/messages/messages_en.yml b/e2e/platforms/fabric/configs/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/fabric/configs/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/platforms/sponge/configs/banmanager/config.yml b/e2e/platforms/sponge/configs/banmanager/config.yml index 41de43066..663f077e8 100644 --- a/e2e/platforms/sponge/configs/banmanager/config.yml +++ b/e2e/platforms/sponge/configs/banmanager/config.yml @@ -1,5 +1,9 @@ # # Aliases will be found and blocked automatically, e.g. msg will block tell +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/sponge/configs/banmanager/messages/messages_de.yml b/e2e/platforms/sponge/configs/banmanager/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/sponge/configs/banmanager/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/sponge/configs/banmanager/messages/messages_en.yml b/e2e/platforms/sponge/configs/banmanager/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/sponge/configs/banmanager/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/platforms/sponge7/configs/banmanager/config.yml b/e2e/platforms/sponge7/configs/banmanager/config.yml index 41de43066..663f077e8 100644 --- a/e2e/platforms/sponge7/configs/banmanager/config.yml +++ b/e2e/platforms/sponge7/configs/banmanager/config.yml @@ -1,5 +1,9 @@ # # Aliases will be found and blocked automatically, e.g. msg will block tell +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/sponge7/configs/banmanager/messages/messages_de.yml b/e2e/platforms/sponge7/configs/banmanager/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/sponge7/configs/banmanager/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/sponge7/configs/banmanager/messages/messages_en.yml b/e2e/platforms/sponge7/configs/banmanager/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/sponge7/configs/banmanager/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/platforms/velocity/configs/banmanager/config.yml b/e2e/platforms/velocity/configs/banmanager/config.yml index 8581b02e3..4c59ef67e 100644 --- a/e2e/platforms/velocity/configs/banmanager/config.yml +++ b/e2e/platforms/velocity/configs/banmanager/config.yml @@ -1,5 +1,9 @@ # # Aliases will be found and blocked automatically, e.g. msg will block tell +locale: + default: en + perPlayer: true + debug: false databases: local: diff --git a/e2e/platforms/velocity/configs/banmanager/messages/messages_de.yml b/e2e/platforms/velocity/configs/banmanager/messages/messages_de.yml new file mode 100644 index 000000000..d813fecbc --- /dev/null +++ b/e2e/platforms/velocity/configs/banmanager/messages/messages_de.yml @@ -0,0 +1,16 @@ +messages: + configReloaded: '&aKonfiguration erfolgreich neu geladen!' + + ban: + player: + disallowed: '&6Du wurdest von diesem Server gebannt wegen &4[reason]' + kick: '&6Du wurdest dauerhaft gebannt wegen &4[reason]' + notify: '&6[player] wurde dauerhaft gebannt von [actor] wegen &4[reason]' + + unban: + notify: '&6[player] wurde von [actor] entbannt' + + mute: + player: + disallowed: '&6Du bist stummgeschaltet wegen &4[reason]' + notify: '&6[player] wurde dauerhaft stummgeschaltet von [actor] wegen &4[reason]' diff --git a/e2e/platforms/velocity/configs/banmanager/messages/messages_en.yml b/e2e/platforms/velocity/configs/banmanager/messages/messages_en.yml new file mode 100644 index 000000000..205a94c13 --- /dev/null +++ b/e2e/platforms/velocity/configs/banmanager/messages/messages_en.yml @@ -0,0 +1,448 @@ +# Variables +# [reason] = Ban/Mute reason +# [player] = The name of the player +# [ip] = The banned ip +# [actor] = Who banned/muted +# [expires] = How long until the ban/mute ends + +messages: + duplicateIP: '&cWarning: [player] has the same IP as the following banned players:\n&6[players]' + duplicateIPAlts: '&cWarning: [player] has the same IP as the following players:\n&6[players]' + configReloaded: '&aConfiguration reloaded successfully!' + deniedNotify: + player: '&cWarning: [player] attempted to join the server but was denied due to &4[reason]' + ip: '&cWarning: [ip] attempted to join the server but was denied due to &4[reason]' + deniedMaxIp: '&cToo many players with your ip address online' + deniedMultiaccounts: '&cToo many players with your ip address logged in recently' + deniedCountry: '&cYou may not connect from your region' + + time: + now: 'now' + year: 'year' + years: 'years' + month: 'month' + months: 'months' + week: 'week' + weeks: 'weeks' + day: 'day' + days: 'days' + hour: 'hour' + hours: 'hours' + minute: 'minute' + minutes: 'minutes' + second: 'second' + seconds: 'seconds' + never: 'never' + error: + invalid: '&cYour time length is invalid' + limit: '&cYou cannot perform this action for that length of time' + + none: 'none' + # General command text + sender: + error: + notFound: '&c[player] not found, are you sure they exist?' + offline: '&c[player] is offline' + noSelf: '&cYou cannot perform that action on yourself!' + exception: '&cAn error occured whilst attempting to perform this command. Please check the console for further details.' + invalidIp: '&cInvalid IP address, expecting w.x.y.z format' + offlinePermission: '&cYou are not allowed to perform this action on an offline player' + ambiguousPlayer: '&cMultiple players match "[player]". Please use their full name.' + exempt: '&c[player] is exempt from that action' + noPermission: '&cYou do not have permission to perform that action' + invalidReason: '&c[reason] is no valid reason.' + # Commands + alts: + header: 'Possible alts found:' + + names: + header: 'Known names for [player]:' + row: '&e[name] &7(first: [firstSeen], last: [lastSeen])' + dateTimeFormat: 'dd-MM-yyyy' + none: '&7No name history found' + + export: + error: + inProgress: '&cAn export is already in progress, please wait' + player: + started: '&aPlayer ban export started' + finished: '&aPlayer ban export finished, file [file] created' + ip: + started: '&aIP ban export started' + finished: '&aIP ban export finished, file [file] created' + + import: + error: + inProgress: '&cAn import is already in progress, please wait' + player: + started: '&aPlayer ban import started' + finished: '&aPlayer ban import finished' + ip: + started: '&aIP ban import started' + finished: '&aIP ban import finished' + advancedban: + started: '&aAdvancedBan import started' + finished: '&aAdvancedBan import finished' + h2: + started: '&aH2 import started' + finished: '&aH2 import finished, please restart the server' + litebans: + started: '&aLiteBans import started' + finished: '&aLiteBans import finished' + + info: + error: + invalidIndex: '&cInvalid player option used' + indexRequired: '&cMultiple players named [name] found, please select a player by providing an index between 1 and [size], e.g. /bminfo [name] 1' + index: '&7#[index] - &6[name] - &4[uuid]' + stats: + player: '&6[player] has been banned [bans] times, muted [mutes] times, kicked [kicks] times and warned [warns] + times ([warnPoints] Points), has [notes] notes and been reported [reports] times' + ip: '&6This ip has been banned [bans] times, muted [mutes] times and range banned [rangebans] times' + connection: '&6Their last connection was with [ip] on [lastSeen]' + geoip: 'Country: [country] City: [city]' + ban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipban: + permanent: '&6Currently banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + iprangeban: + permanent: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created]' + temporary: '&6[from] - [to] banned for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + ipmute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + mute: + permanent: '&6Currently muted for &4[reason]&6 by [actor] at [created]' + temporary: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires]' + temporaryOnline: '&6Currently muted for &4[reason]&6 by [actor] at [created] which expires in [expires] (online time)' + temporaryOnlinePaused: '&6Currently muted for &4[reason]&6 by [actor] at [created] with [remaining] remaining (online time, paused)' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + website: + player: 'https://yourdomain.com/player/[uuid]' + ip: 'http://yourdomain.com/index.php?action=viewip&ip=[ip]&server=0' + history: + row: '&7#[id] &a[&f[type]&a] &6[actor]&f [meta] [reason] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + ips: + row: '&e[ip] - &6[join] - [leave]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + + kick: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: '&6[player] has been kicked by [actor]' + reason: '&6[player] has been kicked by [actor] for &4[reason]' + + kickall: + player: + noReason: '&6You have been kicked' + reason: '&6You have been kicked for &4[reason]' + notify: + noReason: 'All players have been kicked by [actor]' + reason: 'All players have been kicked by [actor] for &4[reason]' + + ban: + player: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[player] is already banned' + cooldown: '&cThis player was banned too recently, try again later' + + banall: + notify: '&6[player] will be permanently banned by [actor] for &4[reason]' + + tempban: + player: + disallowed: '&6You have been temporarily banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[player] has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanall: + notify: '&6[player] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unban: + notify: '&6[player] has been unbanned by [actor]' + error: + noExists: '&c[player] is not banned' + notOwn: '&c[player] was not banned by you, unable to unban' + + unbanall: + notify: '&6[player] will be unbanned by [actor]' + + mute: + player: + blocked: '&cYou may not use the [command] command whilst muted!' + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[player] has been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + cooldown: '&cThis player was muted too recently, try again later' + + muteip: + ip: + disallowed: '&6You have been permanently muted for &4[reason] &6by [actor]' + broadcast: '&4[Muted] [player]&7 [message]' + notify: '&6[ip] ([players]) have been permanently muted by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + muteall: + notify: '&6[player] will be permanently muted by [actor] for &4[reason]' + + tempmute: + player: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + disallowedOnline: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires] (online time)' + notify: '&6[player] has been temporarily muted for [expires] by [actor] for &4[reason]' + notifyOnline: '&6[player] has been temporarily muted for [expires] (online time) by [actor] for &4[reason]' + error: + exists: '&c[player] is already muted' + + tempmuteip: + ip: + disallowed: '&6You have been temporarily muted for &4[reason] &6by [actor] which expires in [expires]' + notify: '&6[ip] ([players]) have been temporarily muted for [expires] by [actor] for &4[reason]' + error: + exists: '&c[ip] is already muted' + + tempmuteall: + notify: '&6[player] will be temporarily muted for [expires] by [actor] for &4[reason]' + + unmute: + notify: '&6[player] has been unmuted by [actor]' + player: '&6You have been unmuted by [actor]' + error: + noExists: '&c[player] is not muted' + notOwn: '&c[player] was not muted by you, unable to unmute' + + unmuteip: + notify: '&6[ip] has been unmuted by [actor]' + error: + noExists: '&c[ip] is not muted' + notOwn: '&c[ip] was not muted by you, unable to unmute' + + unmuteall: + notify: '&6[player] will be unmuted by [actor]' + + banname: + name: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been permanently banned by [actor] for &4[reason]' + error: + exists: '&cName [name] is already banned' + + tempbanname: + name: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6Name [name] has been temporarily banned for [expires] by [actor] for &4[reason]' + + unbanname: + notify: '&6Name [name] has been unbanned by [actor]' + error: + noExists: '&cName [name] is not banned' + + banip: + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been permanently banned by [actor] for &4[reason]' + error: + exists: '&c[ip] is already banned' + cooldown: '&cThis ip was banned too recently, try again later' + + baniprange: + error: + invalid: '&cInvalid range, please use cidr notation 192.168.0.1/16 or wildcard 192.168.*.*' + minMax: '&cRange must be lowest to highest' + exists: '&cA ban containing those ranges already exists' + ip: + disallowed: '&6You have been banned from this server for &4[reason]' + kick: '&6You have been banned permanently for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[from] - [to] have been banned by [actor]' + + tempbaniprange: + notify: '&6[from] - [to] has been temporarily banned for [expires] by [actor]' + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for [expires] by [actor] for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + + unbaniprange: + notify: '&6[from] - [to] has been unbanned by [actor]' + + banipall: + notify: '&6[ip] will be permanently banned by [actor] for &4[reason]' + + tempbanip: + ip: + disallowed: '&6You have been banned from this server for &4[reason] \n&6It expires in [expires]' + kick: '&6You have been temporarily banned for &4[reason]' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: '&6[ip] ([players]) has been temporarily banned for [expires] by [actor] for &4[reason]' + + tempbanipall: + notify: '&6[ip] will be temporarily banned for [expires] by [actor] for &4[reason]' + + unbanip: + notify: '&6[ip] has been unbanned by [actor]' + error: + noExists: '&c[ip] is not banned' + notOwn: '&c[ip] was not banned by you, unable to unban' + + unbanipall: + notify: '&6[ip] will be unbanned by [actor]' + + warn: + player: + warned: '&6You have been warned by [actor] for &4[reason]' + disallowed: + header: '&cYou may not speak until you have accepted your most recent warning. Please type the following:' + reason: '&6[reason]' + removed: '&aThank you for your understanding, you may now speak again' + notify: '&6[player] has been warned by [actor] for &4[reason]' + error: + cooldown: '&cThis player was warned too recently, try again later' + + tempwarn: + player: + warned: '&6You have been warned for [expires] by [actor] for &4[reason]' + notify: '&6[player] has been warned for [expires] by [actor] for &4[reason]' + + dwarn: + player: + notify: '&6Your most recent warning has been deleted by &4[actor]' + notify: '&cThe most recent warning for [player] has been deleted' + error: + noWarnings: '&c[player] has no warnings to delete' + + bmclear: + notify: '&c[player] has had their [type] cleared' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + + bmutils: + missingplayers: + notify: '&c[amount] missing players added' + noneFound: '&a0 missing players found' + found: '&c[amount] missing player data found. Fixing...' + error: + failedLookup: '&cFailed to lookup player [uuid], check server logs' + complete: '&a[amount] players resolved, please restart your server for failed punishments to take affect' + duplicates: + lookup: + notFound: '&aNo duplicate player names found' + error: + invalidName: '&cInvalid name, must be 16 characters or less and contain only letters, numbers and an underscore' + nameExists: '&cA player with that name already exists' + success: '&aPlayer name set to [player] successfully' + + bmrollback: + notify: '&c[player] has had their [type] actions undone' + error: + invalid: '&cInvalid type [type], please choose between [types]' + + sync: + player: + started: '&aStarting force [type] synchronisation' + finished: '&aForced [type] synchronisation complete' + + update: + notify: '&6[BanManager] &aAn update is available' + + notes: + header: '&6[player] has the following notes:' + joinAmount: '&6[player] has &e[amount] &6notes, click to view them' + note: '&6[[player]] &e[message] - &e[created]' + playerNote: '&a[[player]] &6[[actor]] &e[message] - &e[created]' + dateTimeFormat: 'dd-MM-yyyy' + notify: '[player] has a new note attached by [actor]: [message]' + error: + noNotes: '&c[player] has no notes' + noOnlineNotes: '&cNo online players have notes' + + report: + notify: '&6[player] has been reported by [actor] for &4[reason]' + error: + cooldown: '&cThis player was reported too recently, try again later' + assign: + player: '&aReport [id] assigned to [player]' + notify: '&aYou have been assigned report [id] by [actor]' + unassign: + player: '&aReport [id] unassigned' + close: + notify: + closed: '&aReport [id] closed by [actor]' + command: '&aReport [id] closed by [actor] with [command]' + comment: '&aReport [id] closed by [actor] with [comment]' + dispatch: 'Executing command [command]' + list: + noResults: '&cNo reports found' + error: + invalidState: '&cReport state [state] not found' + row: + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + header: '&e-- Reports ([count]) -- Page ([page]/[maxPage])' + all: '&7#[id] &e[[state]] &6- [created] - [player]' + tp: + error: + notFound: '&cReport not found' + worldNotFound: '&cWorld [world] could not be found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + info: + error: + notFound: '&cReport not found' + invalidId: '&c[id] is not a valid report id' + dateTimeFormat: 'yyyy-MM-dd HH:mm:ss' + notify: + report: '&7#[id] &6[actor] reported [player] for &4[reason]&6 at [created]' + location: '[world] - [x], [y], [z]' + + addnoteall: + notify: '&c[player] will have a new attached by [actor]: [message]' + + banlist: + header: '&6There are [bans] [type] bans:' + + bmactivity: + row: + all: '&a[&f[type]&a] &6[player]&f - &6[actor]&f - &e[created]' + player: '&a[&f[type]&a] &6[player]&f - &e[created]' + dateTimeFormat: 'dd-MM-yyyy HH:mm:ss' + noResults: '&cNo results found' + + bmdelete: + notify: '&a[rows] rows deleted' + error: + invalid: '&cInvalid type, please choose between banrecords, muterecords, kicks, notes or warnings' + invalidId: '&c[id] is not a valid number' + + denyalts: + player: + disallowed: '&cThe IP address you are joining from is linked to a banned player' + + reasons: + row: '[hashtag] = [reason]' diff --git a/e2e/sync-configs.sh b/e2e/sync-configs.sh index b4869e2e3..d99c24d01 100755 --- a/e2e/sync-configs.sh +++ b/e2e/sync-configs.sh @@ -7,15 +7,10 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" COMMON_RESOURCES="$SCRIPT_DIR/../common/src/main/resources" -# Files that are shared across platforms (don't need test-specific modifications) +# Only sync files that are truly shared and don't contain test-specific settings. +# Most E2E config files (webhooks.yml, schedules.yml, etc.) are maintained +# per-platform with test-specific values (e.g. webhook-sink URLs, timing). SHARED_FILES=( - "console.yml" - "webhooks.yml" - "exemptions.yml" - "geoip.yml" - "messages.yml" - "reasons.yml" - "schedules.yml" ) # Platform config directories @@ -38,10 +33,15 @@ for config_dir in "${PLATFORM_CONFIGS[@]}"; do cp "$COMMON_RESOURCES/$file" "$config_dir/" fi done + + if [ -d "$COMMON_RESOURCES/messages" ]; then + mkdir -p "$config_dir/messages" + cp "$COMMON_RESOURCES/messages/"*.yml "$config_dir/messages/" + fi fi done -echo "Done! Synced ${#SHARED_FILES[@]} files to ${#PLATFORM_CONFIGS[@]} locations." +echo "Done! Synced messages/ directory to ${#PLATFORM_CONFIGS[@]} locations." echo "" -echo "Note: config.yml files are NOT synced as they contain test-specific settings" -echo " (database host, chatPriority, etc.)" +echo "Note: Only the messages/ directory is synced automatically." +echo " Other config files are maintained per-platform with test-specific settings." diff --git a/e2e/tests/src/locale-smoke.test.ts b/e2e/tests/src/locale-smoke.test.ts new file mode 100644 index 000000000..70d890955 --- /dev/null +++ b/e2e/tests/src/locale-smoke.test.ts @@ -0,0 +1,126 @@ +import { TestBot, createBot } from './helpers/bot' +import { + connectRcon, + disconnectRcon, + banPlayer, + unbanPlayer, + opPlayer, + reloadPlugin, + sendCommand, + isPlayerInList, + isProxy +} from './helpers/rcon' +import { sleep, waitFor } from './helpers/config' + +describe('Locale Smoke Tests', () => { + let staffBot: TestBot + const STAFF_USERNAME = 'LocaleStaff' + const TARGET_USERNAME = 'LocaleTarget' + + beforeAll(async () => { + await connectRcon() + + staffBot = await createBot(STAFF_USERNAME) + await waitFor( + async () => isPlayerInList(STAFF_USERNAME), + { timeout: 10000, interval: 500, message: 'Staff bot not in player list' } + ) + + await opPlayer(STAFF_USERNAME) + await sleep(3500) + }, 120000) + + afterAll(async () => { + try { await unbanPlayer(TARGET_USERNAME) } catch { /* ignore */ } + if (staffBot != null) await staffBot.disconnect() + await disconnectRcon() + }) + + beforeEach(async () => { + staffBot.clearChatHistory() + staffBot.clearSystemMessages() + + try { await unbanPlayer(TARGET_USERNAME) } catch { /* ignore */ } + await sleep(3000) + }) + + test('ban notification uses default locale messages', async () => { + staffBot.clearSystemMessages() + + await banPlayer(TARGET_USERNAME, 'locale-test') + + await waitFor( + () => staffBot.getSystemMessages().some(m => + m.message.toLowerCase().includes('banned') && + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) + ), + { timeout: 10000, interval: 200, message: 'Ban notification not received' } + ) + + const banNotification = staffBot.getSystemMessages().find(m => + m.message.toLowerCase().includes('banned') && + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) + ) + + expect(banNotification).toBeDefined() + expect(banNotification!.message).toContain('permanently banned') + expect(banNotification!.message).toContain('locale-test') + }, 30000) + + test('messages survive bmreload', async () => { + await reloadPlugin() + await sleep(2000) + + staffBot.clearSystemMessages() + + await banPlayer(TARGET_USERNAME, 'post-reload') + + await waitFor( + () => staffBot.getSystemMessages().some(m => + m.message.toLowerCase().includes('banned') && + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) + ), + { timeout: 10000, interval: 200, message: 'Ban notification not received after reload' } + ) + + const banNotification = staffBot.getSystemMessages().find(m => + m.message.toLowerCase().includes('banned') && + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) + ) + + expect(banNotification).toBeDefined() + expect(banNotification!.message).toContain('permanently banned') + expect(banNotification!.message).toContain('post-reload') + }, 30000) + + test('non-default locale messages loaded after reload', async () => { + // Reload to ensure messages/ directory is scanned (including messages_de.yml) + const reloadResponse = await reloadPlugin() + await sleep(2000) + + // The reload should succeed without error, confirming the i18n + // message loading pipeline works with multiple locale files + const isSponge = (process.env.SERVER_HOST ?? 'localhost').toLowerCase().includes('sponge') + if (!isSponge) { + expect(reloadResponse.toLowerCase()).toContain('reloaded') + } + + // Verify the server still functions correctly after loading multiple locales + staffBot.clearSystemMessages() + await banPlayer(TARGET_USERNAME, 'multi-locale') + + await waitFor( + () => staffBot.getSystemMessages().some(m => + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) + ), + { timeout: 10000, interval: 200, message: 'Ban notification not received with multiple locales loaded' } + ) + + const notification = staffBot.getSystemMessages().find(m => + m.message.toLowerCase().includes(TARGET_USERNAME.toLowerCase()) && + m.message.toLowerCase().includes('multi-locale') + ) + + expect(notification).toBeDefined() + }, 30000) +}) diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java index a81e05645..56a0508ee 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/FabricPlayer.java @@ -15,6 +15,7 @@ import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.util.UUIDUtils; //? if >=1.21.1 import net.minecraft.network.packet.s2c.play.PositionFlag; @@ -134,6 +135,18 @@ public boolean isOnline() { return getPlayer() != null && !getPlayer().isDisconnected(); } + @Override + public String getLocale() { + ServerPlayerEntity p = getPlayer(); + if (p == null) return "en"; + //? if >=1.21 { + return MessageRegistry.normaliseLocale(p.getClientOptions().language()); + //?} else { + /*// No public API for client language pre-1.21; fall back to default + return "en"; + *///?} + } + private ServerPlayerEntity getPlayer() { if (player != null) return player; if (isOnlineMode()) return this.server.getPlayerManager().getPlayer(uuid); diff --git a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java index 85695a04d..eb6ac779c 100644 --- a/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java +++ b/fabric/src/main/java/me/confuser/banmanager/fabric/listeners/JoinListener.java @@ -59,8 +59,9 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - - handleDeny(message); + String locale = player.getLocale() != null ? player.getLocale() : "en"; + isDenied = true; + handler.disconnect(FabricServer.formatMessage(message.resolve(locale))); } @Override diff --git a/sponge-api7/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java b/sponge-api7/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java index 47ac7a78d..d986cca7e 100644 --- a/sponge-api7/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java +++ b/sponge-api7/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java @@ -7,6 +7,7 @@ import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.kyori.text.serializer.gson.GsonComponentSerializer; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.util.UUIDUtils; import org.spongepowered.api.Sponge; import org.spongepowered.api.entity.living.player.Player; @@ -136,6 +137,15 @@ public boolean canSee(CommonPlayer player) { return getPlayer().canSee(Sponge.getServer().getPlayer(player.getUniqueId()).get()); } + @Override + public String getLocale() { + Player p = getPlayer(); + if (p == null) return "en"; + java.util.Locale locale = p.getLocale(); + if (locale == null) return "en"; + return MessageRegistry.normaliseLocale(locale.toString()); + } + private Player getPlayer() { if (isOnlineMode()) { Optional player = Sponge.getServer().getPlayer(uuid); diff --git a/sponge-api7/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java b/sponge-api7/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java index a3664d6d8..1e8ed1573 100644 --- a/sponge-api7/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java +++ b/sponge-api7/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java @@ -58,8 +58,9 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - - handleDeny(message); + String locale = player.getLocale() != null ? player.getLocale() : "en"; + event.setCancelled(true); + event.setMessage(SpongeServer.formatMessage(message.resolve(locale))); } @Override diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java index 82e04490b..187d928e7 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/SpongePlayer.java @@ -6,6 +6,7 @@ import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.kyori.text.TextComponent; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.util.UUIDUtils; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -170,6 +171,13 @@ public boolean canSee(CommonPlayer player) { return target.map(serverPlayer -> p.canSee(serverPlayer)).orElse(false); } + @Override + public String getLocale() { + ServerPlayer p = getPlayer(); + if (p == null) return "en"; + return MessageRegistry.normaliseLocale(p.locale().toString()); + } + private ServerPlayer getPlayer() { if (player != null && !player.isRemoved()) { return player; diff --git a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java index 3c3b9567f..8efc90a43 100644 --- a/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java +++ b/sponge/src/main/java/me/confuser/banmanager/sponge/listeners/JoinListener.java @@ -79,7 +79,10 @@ private class BanJoinHandler implements CommonJoinHandler { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - handleDeny(message); + String locale = player.getLocale() != null ? player.getLocale() : "en"; + isDenied = true; + event.setCancelled(true); + event.setMessage(SpongeServer.formatMessage(message.resolve(locale))); } @Override @@ -101,7 +104,7 @@ public void handlePlayerDeny(PlayerData player, Message message) { @Override public void handleDeny(Message message) { - player.kick(SpongeServer.formatMessage(message.toString())); + new SpongePlayer(player, plugin.getConfig().isOnlineMode()).kick(message); } } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java index c0de0b1ef..00158c854 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/VelocityPlayer.java @@ -6,6 +6,7 @@ import me.confuser.banmanager.common.commands.CommonCommand; import me.confuser.banmanager.common.data.PlayerData; import me.confuser.banmanager.common.util.Message; +import me.confuser.banmanager.common.util.MessageRegistry; import me.confuser.banmanager.common.kyori.text.TextComponent; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -110,4 +111,11 @@ public boolean teleport(CommonWorld world, double x, double y, double z, float p public boolean canSee(CommonPlayer player) { return true; } + + @Override + public String getLocale() { + java.util.Locale locale = player.getEffectiveLocale(); + if (locale == null) return "en"; + return MessageRegistry.normaliseLocale(locale.toString()); + } } diff --git a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java index e128d6d18..7b3b9c842 100644 --- a/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java +++ b/velocity/src/main/java/me/confuser/banmanager/velocity/listeners/JoinListener.java @@ -62,7 +62,7 @@ public void handleDeny(Message message) { @Override public void handlePlayerDeny(PlayerData player, Message message) { plugin.getServer().callEvent("PlayerDeniedEvent", player, message); - event.setResult(ResultedEvent.ComponentResult.denied(VelocityServer.formatMessage(message.toString()))); + event.setResult(ResultedEvent.ComponentResult.denied(VelocityServer.formatMessage(message.resolve(player.getLocale() != null ? player.getLocale() : "en")))); } }