diff --git a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java index e38a982db..1ddda6528 100644 --- a/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java +++ b/bootstrap/bukkit/src/main/java/org/geysermc/platform/bukkit/GeyserBukkitConfiguration.java @@ -101,6 +101,11 @@ public class GeyserBukkitConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public String getDefaultLocale() { + return config.getString("default-locale", "en_us"); + } + @Override public Path getFloodgateKeyFile() { return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); diff --git a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java index 94580e583..420f8347e 100644 --- a/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java +++ b/bootstrap/bungeecord/src/main/java/org/geysermc/platform/bungeecord/GeyserBungeeConfiguration.java @@ -102,6 +102,11 @@ public class GeyserBungeeConfiguration implements IGeyserConfiguration { return config.getBoolean("allow-third-party-capes", true); } + @Override + public String getDefaultLocale() { + return config.getString("default-locale", "en_us"); + } + @Override public Path getFloodgateKeyFile() { return Paths.get(dataFolder.toString(), config.getString("floodgate-key-file", "public-key.pem")); diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java index dbc83fbea..7b6a89f15 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongeConfiguration.java @@ -105,6 +105,11 @@ public class GeyserSpongeConfiguration implements IGeyserConfiguration { return node.getNode("allow-third-party-capes").getBoolean(true); } + @Override + public String getDefaultLocale() { + return node.getNode("default-locale").getString("en_us"); + } + @Override public Path getFloodgateKeyFile() { return Paths.get(dataFolder.toString(), node.getNode("floodgate-key-file").getString("public-key.pem")); diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java index 06cb711c1..a1362c7a7 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserConfiguration.java @@ -63,6 +63,9 @@ public class GeyserConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("default-locale") + private String defaultLocale; + private MetricsInfo metrics; @Override diff --git a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java index 2fab448d9..920c65379 100644 --- a/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java +++ b/bootstrap/velocity/src/main/java/org/geysermc/platform/velocity/GeyserVelocityConfiguration.java @@ -63,6 +63,9 @@ public class GeyserVelocityConfiguration implements IGeyserConfiguration { @JsonProperty("allow-third-party-capes") private boolean allowThirdPartyCapes; + @JsonProperty("default-locale") + private String defaultLocale; + private MetricsInfo metrics; @Override diff --git a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java index db5d831b1..41534457e 100644 --- a/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java +++ b/common/src/main/java/org/geysermc/common/IGeyserConfiguration.java @@ -46,6 +46,8 @@ public interface IGeyserConfiguration { boolean isAllowThirdPartyCapes(); + String getDefaultLocale(); + Path getFloodgateKeyFile(); IMetricsInfo getMetrics(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index b398285b8..749dacb2c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -65,6 +65,7 @@ import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.Registry; import org.geysermc.connector.network.translators.block.BlockTranslator; import org.geysermc.connector.utils.ChunkUtils; +import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.Toolbox; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -252,6 +253,17 @@ public class GeyserSession implements CommandSender { connector.getLogger().info(authData.getName() + " (logged in as: " + protocol.getProfile().getName() + ")" + " has connected to remote java server on address " + remoteServer.getAddress()); playerEntity.setUuid(protocol.getProfile().getId()); playerEntity.setUsername(protocol.getProfile().getName()); + + String locale = clientData.getLanguageCode(); + + // Let the user know there locale may take some time to download + // as it has to be extracted from a JAR + if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) { + sendMessage("Downloading your locale (en_us) this may take some time"); + } + + // Download and load the language for the player + LocaleUtils.downloadAndLoadLocale(locale); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java index 226bd9714..ae58830c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaChatTranslator.java @@ -34,6 +34,8 @@ import com.github.steveice10.mc.protocol.data.message.TranslationMessage; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerChatPacket; import com.nukkitx.protocol.bedrock.packet.TextPacket; +import java.util.List; + @Translator(packet = ServerChatPacket.class) public class JavaChatTranslator extends PacketTranslator { @@ -58,14 +60,20 @@ public class JavaChatTranslator extends PacketTranslator { break; } + String locale = session.getClientData().getLanguageCode(); + if (packet.getMessage() instanceof TranslationMessage) { textPacket.setType(TextPacket.Type.TRANSLATION); textPacket.setNeedsTranslation(true); - textPacket.setParameters(MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams())); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + List paramsTranslated = MessageUtils.getTranslationParams(((TranslationMessage) packet.getMessage()).getTranslationParams(), locale); + textPacket.setParameters(paramsTranslated); + + textPacket.setMessage(MessageUtils.insertParams(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false), paramsTranslated)); } else { textPacket.setNeedsTranslation(false); - textPacket.setMessage(MessageUtils.getBedrockMessage(packet.getMessage())); + + textPacket.setMessage(MessageUtils.getTranslatedBedrockMessage(packet.getMessage(), locale, false)); } session.getUpstream().sendPacket(textPacket); diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index 3070e743d..0938fa7c0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -65,4 +65,23 @@ public class FileUtils { return file; } + + public static void writeFile(File file, char[] data) throws IOException { + if (!file.exists()) { + file.createNewFile(); + } + + FileOutputStream fos = new FileOutputStream(file); + + for (char c : data) { + fos.write(c); + } + + fos.flush(); + fos.close(); + } + + public static void writeFile(String name, char[] data) throws IOException { + writeFile(new File(name), data); + } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java new file mode 100644 index 000000000..b5329dff9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -0,0 +1,320 @@ +/* + * 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/Geyser + */ + +package org.geysermc.connector.utils; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; + +import java.io.*; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.zip.ZipFile; + +public class LocaleUtils { + + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + + private static final Map ASSET_MAP = new HashMap<>(); + + private static final String DEFAULT_LOCALE = (GeyserConnector.getInstance().getConfig().getDefaultLocale() != null ? GeyserConnector.getInstance().getConfig().getDefaultLocale() : "en_us"); + + private static String smallestURL = ""; + + static { + // Create the locales folder + File localesFolder = new File("locales/"); + localesFolder.mkdir(); + + // Download the latest asset list and cache it + generateAssetCache(); + downloadAndLoadLocale(DEFAULT_LOCALE); + } + + private static void generateAssetCache() { + try { + VersionManifest versionManifest = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody("https://launchermeta.mojang.com/mc/game/version_manifest.json"), VersionManifest.class); + String latestInfoURL = ""; + for (Version version : versionManifest.getVersions()) { + if (version.getId().equals(versionManifest.getLatestVersion().getRelease())) { + latestInfoURL = version.getUrl(); + break; + } + } + + if (latestInfoURL.isEmpty()) { + throw new Exception("Unable to get latest Minecraft version"); + } + + VersionInfo versionInfo = Toolbox.JSON_MAPPER.readValue(WebUtils.getBody(latestInfoURL), VersionInfo.class); + + int currentSize = Integer.MAX_VALUE; + for (VersionDownload download : versionInfo.getDownloads().values()) { + if (download.getUrl().endsWith(".jar") && download.getSize() < currentSize) { + smallestURL = download.getUrl(); + currentSize = download.getSize(); + } + } + + JsonNode assets = Toolbox.JSON_MAPPER.readTree(WebUtils.getBody(versionInfo.getAssetIndex().getUrl())).get("objects"); + + Iterator> assetIterator = assets.fields(); + while (assetIterator.hasNext()) { + Map.Entry entry = assetIterator.next(); + Asset asset = Toolbox.JSON_MAPPER.treeToValue(entry.getValue(), Asset.class); + ASSET_MAP.put(entry.getKey(), asset); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().info("Failed to load locale asset cache: " + (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace())); + } + } + + public static void downloadAndLoadLocale(String locale) { + locale = locale.toLowerCase(); + if (!ASSET_MAP.containsKey("minecraft/lang/" + locale + ".json") && !locale.equals("en_us")) { + GeyserConnector.getInstance().getLogger().warning("Invalid locale requested to download and load: " + locale); + return; + } + + GeyserConnector.getInstance().getLogger().debug("Downloading and loading locale: " + locale); + + downloadLocale(locale); + loadLocale(locale); + } + + private static void downloadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + if (localeFile.exists()) { + GeyserConnector.getInstance().getLogger().debug("Locale already downloaded: " + locale); + return; + } + + // Create the en_us locale + if (locale.equals("en_us")) { + downloadEN_US(localeFile); + + return; + } + + // Get the hash and download the locale + String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + WebUtils.downloadFile("http://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, "locales/" + locale + ".json"); + } + + private static void loadLocale(String locale) { + File localeFile = new File("locales/" + locale + ".json"); + + // Load the locale + if (localeFile.exists()) { + // Read the localefile + InputStream localeStream; + try { + localeStream = new FileInputStream(localeFile); + } catch (FileNotFoundException e) { + throw new AssertionError("Unable to load locale: " + locale + " (" + e.getMessage() + ")"); + } + + // Parse the file as json + JsonNode localeObj; + try { + localeObj = Toolbox.JSON_MAPPER.readTree(localeStream); + } catch (Exception e) { + throw new AssertionError("Unable to load Java lang map for " + locale, e); + } + + // Parse all the locale fields + Iterator> localeIterator = localeObj.fields(); + Map langMap = new HashMap<>(); + while (localeIterator.hasNext()) { + Map.Entry entry = localeIterator.next(); + langMap.put(entry.getKey(), entry.getValue().asText()); + } + + // Insert the locale into the mappings + LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap); + } else { + GeyserConnector.getInstance().getLogger().warning("Missing locale file: " + locale); + } + } + + private static void downloadEN_US(File localeFile) { + try { + // Let the user know we are downloading the JAR + GeyserConnector.getInstance().getLogger().info("Downloading Minecraft JAR to extract en_us locale, please wait... (this may take some time depending on the speed of your internet connection)"); + GeyserConnector.getInstance().getLogger().debug("Download URL: " + smallestURL); + + // Download the smallest JAR (client or server) + WebUtils.downloadFile(smallestURL, "tmp_locale.jar"); + + // Load in the JAR as a zip and extract the file + ZipFile localeJar = new ZipFile("tmp_locale.jar"); + InputStream inputStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); + FileOutputStream outputStream = new FileOutputStream(localeFile); + + // Write the file to the locale dir + int data = inputStream.read(); + while(data != -1){ + outputStream.write(data); + data = inputStream.read(); + } + + // Flush all changes to disk and cleanup + outputStream.flush(); + outputStream.close(); + + inputStream.close(); + localeJar.close(); + + // Delete the nolonger needed client/server jar + Files.delete(Paths.get("tmp_locale.jar")); + } catch (Exception e) { + throw new AssertionError("Unable to download and extract en_us locale!", e); + } + } + + public static String getLocaleString(String messageText, String locale) { + Map localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); + if (localeStrings == null) + localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(DEFAULT_LOCALE); + + return localeStrings.getOrDefault(messageText, messageText); + } + + public static void init() { + // no-op + } +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionManifest { + @JsonProperty("latest") + private LatestVersion latestVersion; + + @JsonProperty("versions") + private List versions; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class LatestVersion { + @JsonProperty("release") + private String release; + + @JsonProperty("snapshot") + private String snapshot; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Version { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionInfo { + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("time") + private String time; + + @JsonProperty("releaseTime") + private String releaseTime; + + @JsonProperty("assetIndex") + private AssetIndex assetIndex; + + @JsonProperty("downloads") + private Map downloads; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class VersionDownload { + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class AssetIndex { + @JsonProperty("id") + private String id; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("size") + private int size; + + @JsonProperty("totalSize") + private int totalSize; + + @JsonProperty("url") + private String url; +} + +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +class Asset { + @JsonProperty("hash") + private String hash; + + @JsonProperty("size") + private int size; +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java index 7b4c84a7e..76563dd6d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/MessageUtils.java @@ -35,21 +35,24 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import org.geysermc.connector.GeyserConnector; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class MessageUtils { - public static List getTranslationParams(Message[] messages) { + public static List getTranslationParams(Message[] messages, String locale) { List strings = new ArrayList<>(); for (Message message : messages) { if (message instanceof TranslationMessage) { TranslationMessage translation = (TranslationMessage) message; - String builder = "%" + translation.getTranslationKey(); - strings.add(builder); + if (locale == null) { + String builder = "%" + translation.getTranslationKey(); + strings.add(builder); + } if (translation.getTranslationKey().equals("commands.gamemode.success.other")) { strings.add(""); @@ -59,11 +62,16 @@ public class MessageUtils { strings.add(" - no permission or invalid command!"); } - strings.addAll(getTranslationParams(translation.getTranslationParams())); + List furtherParams = getTranslationParams(translation.getTranslationParams()); + if (locale != null) { + strings.add(insertParams(LocaleUtils.getLocaleString(translation.getTranslationKey(), locale), furtherParams)); + }else{ + strings.addAll(furtherParams); + } } else { String builder = getFormat(message.getStyle().getFormats()) + - getColor(message.getStyle().getColor()) + - getBedrockMessage(message); + getColor(message.getStyle().getColor()); + builder += getTranslatedBedrockMessage(message, locale, false); strings.add(builder); } } @@ -71,29 +79,63 @@ public class MessageUtils { return strings; } + public static List getTranslationParams(Message[] messages) { + return getTranslationParams(messages, null); + } + public static String getTranslationText(TranslationMessage message) { return getFormat(message.getStyle().getFormats()) + getColor(message.getStyle().getColor()) + "%" + message.getTranslationKey(); } - public static String getBedrockMessage(Message message) { + public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate) { JsonParser parser = new JsonParser(); if (isMessage(message.getText())) { JsonObject object = parser.parse(message.getText()).getAsJsonObject(); message = Message.fromJson(formatJson(object)); } - StringBuilder builder = new StringBuilder(message.getText()); + String messageText = message.getText(); + if (locale != null && shouldTranslate) { + messageText = LocaleUtils.getLocaleString(messageText, locale); + } + + StringBuilder builder = new StringBuilder(messageText); for (Message msg : message.getExtra()) { builder.append(getFormat(msg.getStyle().getFormats())); builder.append(getColor(msg.getStyle().getColor())); if (!(msg.getText() == null)) { - builder.append(getBedrockMessage(msg)); + boolean isTranslationMessage = (msg instanceof TranslationMessage); + builder.append(getTranslatedBedrockMessage(msg, locale, isTranslationMessage)); } } return builder.toString(); } + public static String getBedrockMessage(Message message) { + return getTranslatedBedrockMessage(message, null, false); + } + + public static String insertParams(String message, List params) { + String newMessage = message; + + Pattern p = Pattern.compile("%([1-9])\\$s"); + Matcher m = p.matcher(message); + while (m.find()) { + try { + newMessage = newMessage.replaceFirst("%" + m.group(1) + "\\$s" , params.get(Integer.parseInt(m.group(1)) - 1)); + } catch (Exception e) { + // Couldnt find the param to replace + } + } + + for (String text : params) { + newMessage = newMessage.replaceFirst("%s", text); + } + + return newMessage; + } + private static String getColor(ChatColor color) { String base = "\u00a7"; switch (color) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 45802196b..9c1e10f6c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -40,7 +40,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.item.ItemEntry; -import java.io.InputStream; +import java.io.*; import java.util.*; public class Toolbox { @@ -52,6 +52,8 @@ public class Toolbox { public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + public static final Map> LOCALE_MAPPINGS = new HashMap<>(); + static { /* Load biomes */ InputStream biomestream = GeyserConnector.class.getClassLoader().getResourceAsStream("bedrock/biome_definitions.dat"); @@ -103,6 +105,9 @@ public class Toolbox { entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); itemIndex++; } + + // Load the locale data + LocaleUtils.init(); } public static InputStream getResource(String resource) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java new file mode 100644 index 000000000..a155f0b95 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -0,0 +1,77 @@ +/* + * 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/Geyser + */ + +package org.geysermc.connector.utils; + +import org.geysermc.connector.GeyserConnector; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class WebUtils { + + public static String getBody(String reqURL) { + URL url = null; + try { + url = new URL(reqURL); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer content = new StringBuffer(); + + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + content.append("\n"); + } + + in.close(); + con.disconnect(); + + return content.toString(); + } catch (Exception e) { + return e.getMessage(); + } + } + + public static void downloadFile(String reqURL, String fileLocation) { + try { + InputStream in = new URL(reqURL).openStream(); + Files.copy(in, Paths.get(fileLocation), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + throw new AssertionError("Unable to download and save file: " + fileLocation + " (" + reqURL + ")", e); + } + } +} diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index ba5700e27..ae0cbed8d 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -57,6 +57,9 @@ general-thread-pool: 32 # OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes allow-third-party-capes: true +# The default locale if we dont have the one the client requested +default-locale: en_us + # bStats is a stat tracker that is entirely anonymous and tracks only basic information # about Geyser, such as how many people are online, how many servers are using Geyser, # what OS is being used, etc. You can learn more about bStats here: https://bstats.org/. diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 278c73449..efc9db6b7 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 278c73449aeeb4064c7513a68f98a49a5f463f0a +Subproject commit efc9db6b7d51bdf145230933ac23b321ac1c132d