1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-21 07:49:18 +00:00

Merge remote-tracking branch 'camo/languagesupport' into development

# Conflicts:
#	bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java
#	bungee/src/main/java/org/geysermc/floodgate/listener/BungeeListener.java
#	bungee/src/main/java/org/geysermc/floodgate/module/BungeeListenerModule.java
#	common/src/main/java/org/geysermc/floodgate/FloodgatePlatform.java
#	common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java
#	common/src/main/resources/proxy-config.yml
#	pom.xml
#	spigot/src/main/java/org/geysermc/floodgate/SpigotPlatform.java
#	spigot/src/main/java/org/geysermc/floodgate/SpigotPlugin.java
#	velocity/src/main/java/org/geysermc/floodgate/VelocityPlugin.java
#	velocity/src/main/java/org/geysermc/floodgate/listener/VelocityListener.java
This commit is contained in:
Tim203
2020-09-19 17:12:37 +02:00
31 changed files with 372 additions and 69 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "common/src/main/resources/languages"]
path = common/src/main/resources/languages
url = https://github.com/GeyserMC/languages

View File

@@ -1,9 +1,6 @@
package org.geysermc.floodgate.api.player; package org.geysermc.floodgate.api.player;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.*;
import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.UiProfile;
import java.util.UUID; import java.util.UUID;
@@ -77,6 +74,11 @@ public interface FloodgatePlayer {
*/ */
LinkedPlayer getLinkedPlayer(); LinkedPlayer getLinkedPlayer();
/**
* Returns the raw skin of the Bedrock player
*/
RawSkin getRawSkin();
/** /**
* Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer. * Casts the FloodgatePlayer instance to a class that extends FloodgatePlayer.
* *

View File

@@ -50,7 +50,10 @@ public final class BungeePlugin extends Plugin {
platform = injector.getInstance(FloodgatePlatform.class); platform = injector.getInstance(FloodgatePlatform.class);
long endCtm = System.currentTimeMillis(); long endCtm = System.currentTimeMillis();
getLogger().info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); getLogger().info(platform.getLanguageManager().getLocaleStringLog(
"floodgate.core.finish",
endCtm - ctm
));
} }
@Override @Override

View File

@@ -17,6 +17,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.handler.BungeeDataHandler; import org.geysermc.floodgate.handler.BungeeDataHandler;
import org.geysermc.floodgate.util.LanguageManager;
import java.util.UUID; import java.util.UUID;
@@ -24,15 +25,18 @@ public final class BungeeListener implements Listener {
private final BungeeDataHandler dataHandler; private final BungeeDataHandler dataHandler;
private final ProxyFloodgateApi api; private final ProxyFloodgateApi api;
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager languageManager;
public BungeeListener(Plugin plugin, ProxyFloodgateConfig config, public BungeeListener(Plugin plugin, ProxyFloodgateConfig config,
ProxyFloodgateApi api, HandshakeHandler handshakeHandler, ProxyFloodgateApi api, HandshakeHandler handshakeHandler,
AttributeKey<FloodgatePlayer> playerAttribute, FloodgateLogger logger) { AttributeKey<FloodgatePlayer> playerAttribute, FloodgateLogger logger,
LanguageManager languageManager) {
this.dataHandler = new BungeeDataHandler( this.dataHandler = new BungeeDataHandler(
plugin, config, api, handshakeHandler, playerAttribute, logger plugin, config, api, handshakeHandler, playerAttribute, logger
); );
this.api = api; this.api = api;
this.logger = logger; this.logger = logger;
this.languageManager = languageManager;
} }
@EventHandler(priority = EventPriority.LOW) @EventHandler(priority = EventPriority.LOW)
@@ -60,8 +64,9 @@ public final class BungeeListener implements Listener {
FloodgatePlayer player = api.getPlayer(uniqueId); FloodgatePlayer player = api.getPlayer(uniqueId);
if (player != null) { if (player != null) {
player.as(FloodgatePlayerImpl.class).setLogin(false); player.as(FloodgatePlayerImpl.class).setLogin(false);
logger.info("Floodgate player who is logged in as {} {} joined", logger.info(languageManager.getLocaleStringLog("floodgate.ingame.login_name",
player.getCorrectUsername(), player.getCorrectUniqueId()); player.getCorrectUsername(), player.getCorrectUniqueId()));
languageManager.loadFloodgateLocale(player.getLanguageCode());
} }
} }
@@ -77,9 +82,8 @@ public final class BungeeListener implements Listener {
ProxiedPlayer player = event.getPlayer(); ProxiedPlayer player = event.getPlayer();
if (api.removePlayer(player.getUniqueId()) != null) { if (api.removePlayer(player.getUniqueId()) != null) {
api.removeEncryptedData(player.getUniqueId()); api.removeEncryptedData(player.getUniqueId());
logger.info( logger.info(languageManager.getLocaleStringLog(
"Floodgate player who was logged in as {} {} disconnected", "floodgate.ingame.disconnect_name", player.getName())
player.getName(), player.getUniqueId()
); );
} }
} }

View File

@@ -40,6 +40,7 @@ import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.listener.BungeeListener; import org.geysermc.floodgate.listener.BungeeListener;
import org.geysermc.floodgate.register.ListenerRegister; import org.geysermc.floodgate.register.ListenerRegister;
import org.geysermc.floodgate.util.LanguageManager;
import javax.inject.Named; import javax.inject.Named;
@@ -54,7 +55,9 @@ public final class BungeeListenerModule extends AbstractModule {
public Listener bungeeListener(Plugin plugin, ProxyFloodgateConfig config, public Listener bungeeListener(Plugin plugin, ProxyFloodgateConfig config,
ProxyFloodgateApi api, HandshakeHandler handshakeHandler, ProxyFloodgateApi api, HandshakeHandler handshakeHandler,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute, @Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger) { FloodgateLogger logger, LanguageManager languageManager) {
return new BungeeListener(plugin, config, api, handshakeHandler, playerAttribute, logger); return new BungeeListener(
plugin, config, api, handshakeHandler, playerAttribute, logger, languageManager
);
} }
} }

View File

@@ -49,6 +49,7 @@ import org.geysermc.floodgate.platform.command.CommandRegistration;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.util.BungeeCommandUtil; import org.geysermc.floodgate.util.BungeeCommandUtil;
import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class BungeePlatformModule extends AbstractModule { public final class BungeePlatformModule extends AbstractModule {
@@ -96,8 +97,8 @@ public final class BungeePlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public CommandUtil commandUtil(FloodgateLogger logger) { public CommandUtil commandUtil(FloodgateLogger logger, LanguageManager languageManager) {
return new BungeeCommandUtil(logger); return new BungeeCommandUtil(logger, languageManager);
} }
@Provides @Provides

View File

@@ -30,7 +30,9 @@ import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandMessage;
import org.geysermc.floodgate.platform.command.util.CommandResponseCache; import org.geysermc.floodgate.platform.command.util.CommandResponseCache;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
@@ -38,10 +40,21 @@ import org.geysermc.floodgate.platform.command.util.CommandUtil;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class BungeeCommandUtil extends CommandResponseCache<BaseComponent[]> implements CommandUtil { public final class BungeeCommandUtil extends CommandResponseCache<BaseComponent[]> implements CommandUtil {
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager manager;
@Override @Override
public void sendMessage(Object player, CommandMessage message, Object... args) { public void sendMessage(Object player, CommandMessage message, Object... args) {
cast(player).sendMessage(getOrAddCachedMessage(message, args)); ProxiedPlayer proxiedPlayer = cast(player);
FloodgatePlayer floodgatePlayer =
FloodgateApi.getInstance().getPlayer(proxiedPlayer.getUniqueId());
if (floodgatePlayer != null) {
proxiedPlayer.sendMessage(transformMessage(
manager.getPlayerLocaleString(message.getMessage(),
floodgatePlayer.getLanguageCode(), args)));
} else {
proxiedPlayer.sendMessage(transformMessage(
manager.getLocaleStringLog(message.getMessage(), args)));
}
} }
@Override @Override

View File

@@ -42,6 +42,7 @@ import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.module.ConfigLoadedModule; import org.geysermc.floodgate.module.ConfigLoadedModule;
import org.geysermc.floodgate.module.PostInitializeModule; import org.geysermc.floodgate.module.PostInitializeModule;
import org.geysermc.floodgate.util.LanguageManager;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -55,6 +56,9 @@ public class FloodgatePlatform {
private final FloodgateLogger logger; private final FloodgateLogger logger;
@Getter(AccessLevel.PROTECTED)
private final LanguageManager languageManager;
private final Injector guice; private final Injector guice;
@Inject private PlatformInjector injector; @Inject private PlatformInjector injector;
@@ -63,9 +67,11 @@ public class FloodgatePlatform {
public FloodgatePlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api, public FloodgatePlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api,
ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader, ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader,
HandshakeHandler handshakeHandler, FloodgateLogger logger, HandshakeHandler handshakeHandler, FloodgateLogger logger,
PlatformInjector platformInjector, Injector injector) { PlatformInjector platformInjector, LanguageManager languageManager,
Injector injector) {
this.api = api; this.api = api;
this.logger = logger; this.logger = logger;
this.languageManager = languageManager;
if (!Files.isDirectory(dataDirectory)) { if (!Files.isDirectory(dataDirectory)) {
try { try {
@@ -86,6 +92,7 @@ public class FloodgatePlatform {
guice.injectMembers(playerLinkLoader); guice.injectMembers(playerLinkLoader);
guice.injectMembers(handshakeHandler); guice.injectMembers(handshakeHandler);
guice.injectMembers(languageManager);
PlayerLink link = playerLinkLoader.load(); PlayerLink link = playerLinkLoader.load();

View File

@@ -53,6 +53,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final InputMode inputMode; private final InputMode inputMode;
private final String ip; private final String ip;
private final LinkedPlayer linkedPlayer; private final LinkedPlayer linkedPlayer;
private final RawSkin rawSkin;
/** /**
* Returns true if the player is still logging in * Returns true if the player is still logging in
@@ -80,6 +81,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
uiProfile = UiProfile.getById(data.getUiProfile()); uiProfile = UiProfile.getById(data.getUiProfile());
inputMode = InputMode.getById(data.getInputMode()); inputMode = InputMode.getById(data.getInputMode());
ip = data.getIp(); ip = data.getIp();
rawSkin = data.getSkin();
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one) // we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (data.hasPlayerLink()) { if (data.hasPlayerLink()) {
@@ -141,6 +143,6 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
public BedrockData toBedrockData() { public BedrockData toBedrockData() {
return new BedrockData(version, username, xuid, deviceOs.ordinal(), languageCode, return new BedrockData(version, username, xuid, deviceOs.ordinal(), languageCode,
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer); uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, rawSkin);
} }
} }

View File

@@ -33,14 +33,15 @@ import org.geysermc.floodgate.platform.command.CommandMessage;
* Messages (or part of messages) that are used in two or more commands and thus are 'commonly used' * Messages (or part of messages) that are used in two or more commands and thus are 'commonly used'
*/ */
public enum CommonCommandMessage implements CommandMessage { public enum CommonCommandMessage implements CommandMessage {
NOT_A_PLAYER("Please head over to your Minecraft Account and link from there."), NOT_A_PLAYER("floodgate.commands.not_a_player"),
CHECK_CONSOLE("Please check the console for more info!"), CHECK_CONSOLE("floodgate.commands.check_console"),
IS_LINKED_ERROR("&cError while checking if the given player is linked. " + CHECK_CONSOLE); // TODO used to also have console check
IS_LINKED_ERROR("floodgate.commands.is_linked_error");
@Getter private final String message; @Getter private final String message;
CommonCommandMessage(String message) { CommonCommandMessage(String message) {
this.message = message.replace('&', COLOR_CHAR); this.message = message;
} }
@Override @Override

View File

@@ -146,26 +146,22 @@ public final class LinkAccountCommand implements Command {
} }
public enum Message implements CommandMessage { public enum Message implements CommandMessage {
ALREADY_LINKED("&cYour account is already linked!\n" + ALREADY_LINKED("floodgate.command.link_account.already_linked"),
"&cIf you want to link to a different account, run &6/unlinkaccount&c and try it again." JAVA_USAGE("floodgate.command.link_account.java_usage"),
), LINK_REQUEST_CREATED("floodgate.command.link_account.link_request_created"),
JAVA_USAGE("&cUsage: /linkaccount <gamertag>"), BEDROCK_USAGE("floodgate.command.link_account.bedrock_usage"),
LINK_REQUEST_CREATED("&aLog in as {} on Bedrock and run &6/linkaccount {} {}\n" + LINK_REQUEST_EXPIRED("floodgate.command.link_account.link_request_expired"),
"&cWarning: Any progress on your Bedrock account will not be carried over! Save any items in your inventory first.\n" + LINK_REQUEST_COMPLETED("floodgate.command.link_account.link_request_completed"),
"&cIf you change your mind you can run &6/unlinkaccount&c to get your progess back." // TODO this also used to have another message
), LINK_REQUEST_ERROR("floodgate.command.link_request.error"),
BEDROCK_USAGE("&cStart the process from Java! Usage: /linkaccount <gamertag>"), INVALID_CODE("floodgate.command.link_account.invalid_code"),
LINK_REQUEST_EXPIRED("&cThe code you entered is expired! Run &6/linkaccount&c again on your Java account"), NO_LINK_REQUESTED("floodgate.command.link_account.no_link_requested"),
LINK_REQUEST_COMPLETED("You are successfully linked to {}!\nIf you want to undo this run /unlinkaccount"), LINK_REQUEST_DISABLED("floodgate.commands.linking_disabled");
LINK_REQUEST_ERROR("&cAn error occurred while linking. " + CommonCommandMessage.CHECK_CONSOLE),
INVALID_CODE("&cInvalid code! Please check your code or run the &6/linkaccount&c command again on your Java account."),
NO_LINK_REQUESTED("&cThis player has not requested an account link! Please log in on Java and request one with &6/linkaccount"),
LINK_REQUEST_DISABLED("&cLinking is not enabled on this server.");
@Getter private final String message; @Getter private final String message;
Message(String message) { Message(String message) {
this.message = message.replace('&', COLOR_CHAR); this.message = message;
} }
} }
} }

View File

@@ -90,15 +90,16 @@ public final class UnlinkAccountCommand implements Command {
} }
public enum Message implements CommandMessage { public enum Message implements CommandMessage {
NOT_LINKED("&cYour account isn't linked"), NOT_LINKED("floodgate.command.unlink_account.not_linked"),
UNLINK_SUCCESS("&cUnlink successful! Rejoin to return to your Bedrock account"), UNLINK_SUCCESS("floodgate.command.unlink_account.unlink_success"),
UNLINK_ERROR("&cAn error occurred while unlinking player! " + CommonCommandMessage.CHECK_CONSOLE), // TODO also used to have CHECK_CONSOLE
LINKING_NOT_ENABLED("&cLinking is not enabled on this server"); UNLINK_ERROR("floodgate.command.unlink_account.error"),
LINKING_NOT_ENABLED("floodgate.commands.linking_disabled");
@Getter private final String message; @Getter private final String message;
Message(String message) { Message(String message) {
this.message = message.replace('&', COLOR_CHAR); this.message = message;
} }
} }
} }

View File

@@ -46,6 +46,9 @@ public class FloodgateConfig {
@JsonProperty(value = "replace-spaces") @JsonProperty(value = "replace-spaces")
private boolean replaceSpaces; private boolean replaceSpaces;
@JsonProperty(value = "default-locale")
private String defaultLocale;
@JsonProperty(value = "disconnect") @JsonProperty(value = "disconnect")
private DisconnectMessages messages; private DisconnectMessages messages;

View File

@@ -37,6 +37,8 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.crypto.KeyProducer; import org.geysermc.floodgate.crypto.KeyProducer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@@ -74,7 +76,13 @@ public class ConfigLoader {
boolean newConfig = !Files.exists(configPath); boolean newConfig = !Files.exists(configPath);
try { try {
if (newConfig) { if (newConfig) {
Files.copy(defaultConfigPath, configPath); InputStream newConfigFile =
ConfigLoader.class.getClassLoader().getResourceAsStream(defaultConfigName);
if (newConfigFile == null) {
throw new RuntimeException("Failed to get the default config file!");
}
Files.copy(newConfigFile, configPath);
Key key = keyProducer.produce(); Key key = keyProducer.produce();
cipher.init(key); cipher.init(key);

View File

@@ -45,6 +45,8 @@ public class ConfigUpdater {
private final ConfigFileUpdater fileUpdater; private final ConfigFileUpdater fileUpdater;
private final FloodgateLogger logger; private final FloodgateLogger logger;
private static final int CONFIG_VERSION = 1;
public void update(Path defaultConfigLocation) { public void update(Path defaultConfigLocation) {
Path configLocation = dataFolder.resolve("config.yml"); Path configLocation = dataFolder.resolve("config.yml");
@@ -70,12 +72,12 @@ public class ConfigUpdater {
int version = (int) versionElement; int version = (int) versionElement;
checkArgument( checkArgument(
version == 1, version == CONFIG_VERSION,
"Config is newer then possible on this version! Expected 1, got " + version "Config is newer then possible on this version! Expected " + CONFIG_VERSION + ", got " + version
); );
// config is already up-to-date // config is already up-to-date
if (version == 1) { if (version == CONFIG_VERSION) {
return; return;
} }
} else { } else {

View File

@@ -45,6 +45,7 @@ import org.geysermc.floodgate.config.updater.ConfigUpdater;
import org.geysermc.floodgate.crypto.*; import org.geysermc.floodgate.crypto.*;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.util.LanguageManager;
import java.nio.file.Path; import java.nio.file.Path;
@@ -94,6 +95,12 @@ public final class CommonModule extends AbstractModule {
return new ConfigUpdater(dataDirectory, configFileUpdater, logger); return new ConfigUpdater(dataDirectory, configFileUpdater, logger);
} }
@Provides
@Singleton
public LanguageManager languageLoader(FloodgateLogger logger) {
return new LanguageManager(logger);
}
@Provides @Provides
@Singleton @Singleton
public PlayerLinkLoader playerLinkLoader() { public PlayerLinkLoader playerLinkLoader() {

View File

@@ -0,0 +1,186 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*
*/
package org.geysermc.floodgate.util;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfig;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
/**
* Manages translations for strings in Floodgate
*/
public class LanguageManager {
private final Map<String, Properties> LOCALE_MAPPINGS = new HashMap<>();
private final FloodgateLogger logger;
/**
* The locale used in console and as a fallback
*/
private String defaultLocale;
public LanguageManager(FloodgateLogger logger) {
this.logger = logger;
}
/**
* Loads the log's locale file once Floodgate loads the config
*
* @param config the Floodgate config
*/
public void initialize(FloodgateConfig config) {
loadFloodgateLocale("en_US"); // Fallback
if (config.getDefaultLocale() != null &&
isValidLanguage(formatLocale(config.getDefaultLocale()))) {
loadFloodgateLocale(formatLocale(config.getDefaultLocale()));
defaultLocale = formatLocale(config.getDefaultLocale());
} else {
String systemLocale = formatLocale(Locale.getDefault().getLanguage() + "_" +
Locale.getDefault().getCountry());
if (isValidLanguage(systemLocale)) {
loadFloodgateLocale(systemLocale);
defaultLocale = systemLocale;
} else {
defaultLocale = "en_US";
}
}
}
/**
* Loads a Floodgate locale from resources; if the file doesn't exist it just logs a warning
*
* @param locale Locale to load
*/
public void loadFloodgateLocale(String locale) {
locale = formatLocale(locale);
InputStream localeStream = LanguageManager.class.getClassLoader().getResourceAsStream(
"languages/texts/" + locale + ".properties");
// Load the locale
if (localeStream != null) {
Properties localeProp = new Properties();
try {
localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8));
} catch (Exception e) {
throw new AssertionError("Failed to load Floodgate locale", e);
}
// Insert the locale into the mappings
LOCALE_MAPPINGS.put(locale, localeProp);
} else {
logger.warn("Missing locale file: " + locale);
}
}
/**
* Get a formatted language string with the default locale for Floodgate
*
* @param key Language string to translate
* @param values Values to put into the string
* @return Translated string or the original message if it was not found in the given locale
*/
public String getLocaleStringLog(String key, Object... values) {
return getPlayerLocaleString(key, defaultLocale, values);
}
/**
* Get a formatted language string with the given locale for Floodgate
*
* @param key Language string to translate
* @param locale Locale to translate to
* @param values Values to put into the string
* @return Translated string or the original message if it was not found in the given locale
*/
public String getPlayerLocaleString(String key, String locale, Object... values) {
locale = formatLocale(locale);
Properties properties = LOCALE_MAPPINGS.get(locale);
String formatString = properties.getProperty(key);
// Try and get the key from the default locale
if (formatString == null) {
properties = LOCALE_MAPPINGS.get(defaultLocale);
formatString = properties.getProperty(key);
}
// Try and get the key from en_US (this should only ever happen in development)
if (formatString == null) {
properties = LOCALE_MAPPINGS.get("en_US");
formatString = properties.getProperty(key);
}
// Final fallback
if (formatString == null) {
formatString = key;
}
return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values);
}
/**
* Cleans up and formats a locale string
*
* @param locale The locale to format
* @return The formatted locale
*/
private static String formatLocale(String locale) {
try {
String[] parts = locale.toLowerCase().split("_");
return parts[0] + "_" + parts[1].toUpperCase();
} catch (Exception e) {
return locale;
}
}
/**
* Ensures that the given locale is supported by Floodgate
* @param locale the locale to validate
* @return true if the given locale is supported by Floodgate
*/
private boolean isValidLanguage(String locale) {
boolean result = true;
if (LanguageManager.class.getResource("/languages/texts/" + locale + ".properties") == null) {
result = false;
logger.warn(locale + " is not a supported Floodgate language.");
} else {
if (!LOCALE_MAPPINGS.containsKey(locale)) {
loadFloodgateLocale(locale);
}
}
return result;
}
}

View File

@@ -11,6 +11,9 @@ username-prefix: "*"
# Should spaces be replaced with '_' in bedrock usernames? # Should spaces be replaced with '_' in bedrock usernames?
replace-spaces: true replace-spaces: true
# The default locale for Floodgate. By default, Floodgate uses the system locale
# default-locale: en_US
disconnect: disconnect:
# The disconnect message Geyser users should get when connecting # The disconnect message Geyser users should get when connecting
# to the server with an invalid key # to the server with an invalid key

View File

@@ -11,6 +11,9 @@ username-prefix: "*"
# Should spaces be replaced with '_' in bedrock usernames? # Should spaces be replaced with '_' in bedrock usernames?
replace-spaces: true replace-spaces: true
# The default locale for Floodgate. By default, Floodgate uses the system locale
# default-locale: en_US
# Should the proxy send the bedrock player data to the servers it is connecting to? # Should the proxy send the bedrock player data to the servers it is connecting to?
# This requires Floodgate to be installed on the servers. # This requires Floodgate to be installed on the servers.
# You'll get kicked if you don't use the plugin. The default value is false because of it # You'll get kicked if you don't use the plugin. The default value is false because of it

View File

@@ -37,6 +37,7 @@ import org.geysermc.floodgate.api.inject.PlatformInjector;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.loader.ConfigLoader; import org.geysermc.floodgate.config.loader.ConfigLoader;
import org.geysermc.floodgate.link.PlayerLinkLoader; import org.geysermc.floodgate.link.PlayerLinkLoader;
import org.geysermc.floodgate.util.LanguageManager;
import java.nio.file.Path; import java.nio.file.Path;
@@ -47,9 +48,10 @@ public final class SpigotPlatform extends FloodgatePlatform {
public SpigotPlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api, public SpigotPlatform(@Named("dataDirectory") Path dataDirectory, FloodgateApi api,
ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader, ConfigLoader configLoader, PlayerLinkLoader playerLinkLoader,
HandshakeHandler handshakeHandler, FloodgateLogger logger, HandshakeHandler handshakeHandler, FloodgateLogger logger,
PlatformInjector platformInjector, Injector injector) { PlatformInjector platformInjector, LanguageManager languageManager,
super(dataDirectory, api, configLoader, playerLinkLoader, Injector injector) {
handshakeHandler, logger, platformInjector, injector); super(dataDirectory, api, configLoader, playerLinkLoader, handshakeHandler,
logger, platformInjector, languageManager, injector);
} }
@Override @Override

View File

@@ -49,7 +49,10 @@ public final class SpigotPlugin extends JavaPlugin {
platform = injector.getInstance(SpigotPlatform.class); platform = injector.getInstance(SpigotPlatform.class);
long endCtm = System.currentTimeMillis(); long endCtm = System.currentTimeMillis();
getLogger().info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); getLogger().info(platform.getLanguageManager().getLocaleStringLog(
"floodgate.core.finish",
endCtm - ctm
));
} }
@Override @Override

View File

@@ -38,6 +38,7 @@ import org.geysermc.floodgate.FloodgatePlayerImpl;
import org.geysermc.floodgate.api.SimpleFloodgateApi; import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.util.LanguageManager;
import java.util.UUID; import java.util.UUID;
@@ -45,6 +46,7 @@ import java.util.UUID;
public final class SpigotListener implements Listener { public final class SpigotListener implements Listener {
private final SimpleFloodgateApi api; private final SimpleFloodgateApi api;
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager languageManager;
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onAsyncPreLogin(AsyncPlayerPreLoginEvent event) { public void onAsyncPreLogin(AsyncPlayerPreLoginEvent event) {
@@ -66,8 +68,9 @@ public final class SpigotListener implements Listener {
FloodgatePlayer player = api.getPlayer(uniqueId); FloodgatePlayer player = api.getPlayer(uniqueId);
if (player != null) { if (player != null) {
player.as(FloodgatePlayerImpl.class).setLogin(false); player.as(FloodgatePlayerImpl.class).setLogin(false);
logger.info("Floodgate player who is logged in as {} {} joined", logger.info(languageManager.getLocaleStringLog("floodgate.ingame.login_name",
player.getCorrectUsername(), player.getCorrectUniqueId()); player.getCorrectUsername(), player.getCorrectUniqueId()));
languageManager.loadFloodgateLocale(player.getLanguageCode());
} }
} }
@@ -75,9 +78,8 @@ public final class SpigotListener implements Listener {
public void onPlayerQuit(PlayerQuitEvent event) { public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
if (api.removePlayer(player.getUniqueId()) != null) { if (api.removePlayer(player.getUniqueId()) != null) {
logger.info( logger.info(languageManager.getLocaleStringLog(
"Floodgate player who was logged in as {} {} disconnected", "floodgate.ingame.disconnect_name", player.getName())
player.getName(), player.getUniqueId()
); );
} }
} }

View File

@@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.listener.SpigotListener; import org.geysermc.floodgate.listener.SpigotListener;
import org.geysermc.floodgate.register.ListenerRegister; import org.geysermc.floodgate.register.ListenerRegister;
import org.geysermc.floodgate.util.LanguageManager;
public final class SpigotListenerModule extends AbstractModule { public final class SpigotListenerModule extends AbstractModule {
@Override @Override
@@ -44,7 +45,8 @@ public final class SpigotListenerModule extends AbstractModule {
@Singleton @Singleton
@ProvidesIntoSet @ProvidesIntoSet
public Listener spigotListener(SimpleFloodgateApi api, FloodgateLogger logger) { public Listener spigotListener(SimpleFloodgateApi api, FloodgateLogger logger,
return new SpigotListener(api, logger); LanguageManager languageManager) {
return new SpigotListener(api, logger, languageManager);
} }
} }

View File

@@ -45,6 +45,7 @@ import org.geysermc.floodgate.logger.JavaUtilFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandRegistration; import org.geysermc.floodgate.platform.command.CommandRegistration;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.SpigotCommandUtil; import org.geysermc.floodgate.util.SpigotCommandUtil;
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -88,8 +89,8 @@ public final class SpigotPlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public CommandUtil commandUtil(FloodgateLogger logger) { public CommandUtil commandUtil(FloodgateLogger logger, LanguageManager languageManager) {
return new SpigotCommandUtil(plugin, logger); return new SpigotCommandUtil(plugin, logger, languageManager);
} }
@Provides @Provides

View File

@@ -30,7 +30,9 @@ import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandMessage;
import org.geysermc.floodgate.platform.command.util.CommandResponseCache; import org.geysermc.floodgate.platform.command.util.CommandResponseCache;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
@@ -39,10 +41,19 @@ import org.geysermc.floodgate.platform.command.util.CommandUtil;
public final class SpigotCommandUtil extends CommandResponseCache<String> implements CommandUtil { public final class SpigotCommandUtil extends CommandResponseCache<String> implements CommandUtil {
private final JavaPlugin plugin; private final JavaPlugin plugin;
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager manager;
@Override @Override
public void sendMessage(Object player, CommandMessage message, Object... args) { public void sendMessage(Object player, CommandMessage message, Object... args) {
cast(player).sendMessage(format(message, args)); Player bukkitPlayer = cast(player);
FloodgatePlayer floodgatePlayer =
FloodgateApi.getInstance().getPlayer(bukkitPlayer.getUniqueId());
if (floodgatePlayer != null) {
bukkitPlayer.sendMessage(manager.getPlayerLocaleString(message.getMessage(),
floodgatePlayer.getLanguageCode(), args));
} else {
bukkitPlayer.sendMessage(manager.getLocaleStringLog(message.getMessage(), args));
}
} }
@Override @Override

View File

@@ -53,7 +53,10 @@ public final class VelocityPlugin {
platform = injector.getInstance(FloodgatePlatform.class); platform = injector.getInstance(FloodgatePlatform.class);
long endCtm = System.currentTimeMillis(); long endCtm = System.currentTimeMillis();
logger.info("Took " + (endCtm - ctm) + "ms to boot Floodgate"); logger.info(platform.getLanguageManager().getLocaleStringLog(
"floodgate.core.finish",
endCtm - ctm
));
} }
@Subscribe @Subscribe

View File

@@ -31,6 +31,7 @@ import com.google.common.cache.CacheBuilder;
import com.velocitypowered.api.event.PostOrder; import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
@@ -42,6 +43,7 @@ import net.kyori.adventure.text.TextComponent;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.util.LanguageManager;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
@@ -58,17 +60,19 @@ public final class VelocityListener {
private final AttributeKey<FloodgatePlayer> playerAttribute; private final AttributeKey<FloodgatePlayer> playerAttribute;
private final AttributeKey<String> kickMessageAttribute; private final AttributeKey<String> kickMessageAttribute;
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager languageManager;
private final Cache<InboundConnection, FloodgatePlayer> playerCache; private final Cache<InboundConnection, FloodgatePlayer> playerCache;
public VelocityListener(ProxyFloodgateApi api, public VelocityListener(ProxyFloodgateApi api,
AttributeKey<FloodgatePlayer> playerAttribute, AttributeKey<FloodgatePlayer> playerAttribute,
AttributeKey<String> kickMessageAttribute, AttributeKey<String> kickMessageAttribute,
FloodgateLogger logger) { FloodgateLogger logger, LanguageManager languageManager) {
this.api = api; this.api = api;
this.playerAttribute = playerAttribute; this.playerAttribute = playerAttribute;
this.kickMessageAttribute = kickMessageAttribute; this.kickMessageAttribute = kickMessageAttribute;
this.logger = logger; this.logger = logger;
this.languageManager = languageManager;
this.playerCache = CacheBuilder.newBuilder() this.playerCache = CacheBuilder.newBuilder()
.maximumSize(500) .maximumSize(500)
@@ -112,6 +116,14 @@ public final class VelocityListener {
} }
} }
@Subscribe
public void onLogin(LoginEvent event) {
FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player != null) {
languageManager.loadFloodgateLocale(player.getLanguageCode());
}
}
@Subscribe(order = PostOrder.LAST) @Subscribe(order = PostOrder.LAST)
public void onDisconnect(DisconnectEvent event) { public void onDisconnect(DisconnectEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
@@ -122,8 +134,9 @@ public final class VelocityListener {
if (fPlayer != null && api.removePlayer(fPlayer)) { if (fPlayer != null && api.removePlayer(fPlayer)) {
api.removeEncryptedData(event.getPlayer().getUniqueId()); api.removeEncryptedData(event.getPlayer().getUniqueId());
logger.info("Floodgate player who was logged in as {} {} disconnected", logger.info(languageManager.getLocaleStringLog(
player.getUsername(), player.getUniqueId()); "floodgate.ingame.disconnect_name", player.getUsername()
));
} }
} catch (Exception exception) { } catch (Exception exception) {
logger.error("Failed to remove the player", exception); logger.error("Failed to remove the player", exception);

View File

@@ -35,6 +35,7 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.listener.VelocityListener; import org.geysermc.floodgate.listener.VelocityListener;
import org.geysermc.floodgate.register.ListenerRegister; import org.geysermc.floodgate.register.ListenerRegister;
import org.geysermc.floodgate.util.LanguageManager;
public final class VelocityListenerModule extends AbstractModule { public final class VelocityListenerModule extends AbstractModule {
@Override @Override
@@ -45,8 +46,9 @@ public final class VelocityListenerModule extends AbstractModule {
@Singleton @Singleton
@ProvidesIntoSet @ProvidesIntoSet
public Object velocityListener(ProxyFloodgateApi api, FloodgateLogger logger, public Object velocityListener(ProxyFloodgateApi api, FloodgateLogger logger,
LanguageManager languageManager,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttr, @Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttr,
@Named("kickMessageAttribute") AttributeKey<String> kickMessageAttr) { @Named("kickMessageAttribute") AttributeKey<String> kickMessageAttr) {
return new VelocityListener(api, playerAttr, kickMessageAttr, logger); return new VelocityListener(api, playerAttr, kickMessageAttr, logger, languageManager);
} }
} }

View File

@@ -49,6 +49,7 @@ import org.geysermc.floodgate.logger.Slf4jFloodgateLogger;
import org.geysermc.floodgate.platform.command.CommandRegistration; import org.geysermc.floodgate.platform.command.CommandRegistration;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
import org.geysermc.floodgate.platform.listener.ListenerRegistration; import org.geysermc.floodgate.platform.listener.ListenerRegistration;
import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.VelocityCommandUtil; import org.geysermc.floodgate.util.VelocityCommandUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -91,8 +92,9 @@ public final class VelocityPlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public VelocityCommandUtil commandUtil(FloodgateLogger logger) { public VelocityCommandUtil commandUtil(FloodgateLogger logger,
return new VelocityCommandUtil(logger); LanguageManager languageManager) {
return new VelocityCommandUtil(logger, languageManager);
} }
@Provides @Provides

View File

@@ -30,7 +30,9 @@ import com.velocitypowered.api.proxy.Player;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.command.CommandMessage; import org.geysermc.floodgate.platform.command.CommandMessage;
import org.geysermc.floodgate.platform.command.util.CommandResponseCache; import org.geysermc.floodgate.platform.command.util.CommandResponseCache;
import org.geysermc.floodgate.platform.command.util.CommandUtil; import org.geysermc.floodgate.platform.command.util.CommandUtil;
@@ -38,10 +40,21 @@ import org.geysermc.floodgate.platform.command.util.CommandUtil;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class VelocityCommandUtil extends CommandResponseCache<TextComponent> implements CommandUtil { public final class VelocityCommandUtil extends CommandResponseCache<TextComponent> implements CommandUtil {
private final FloodgateLogger logger; private final FloodgateLogger logger;
private final LanguageManager manager;
@Override @Override
public void sendMessage(Object player, CommandMessage message, Object... args) { public void sendMessage(Object player, CommandMessage message, Object... args) {
cast(player).sendMessage(getOrAddCachedMessage(message, args)); Player velocityPlayer = cast(player);
FloodgatePlayer floodgatePlayer =
FloodgateApi.getInstance().getPlayer(velocityPlayer.getUniqueId());
if (floodgatePlayer != null) {
velocityPlayer.sendMessage(
transformMessage(manager.getPlayerLocaleString(message.getMessage(),
floodgatePlayer.getLanguageCode(), args)));
} else {
velocityPlayer.sendMessage(
transformMessage(manager.getLocaleStringLog(message.getMessage(), args)));
}
} }
@Override @Override