diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d98b64..c271ce6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ stages: variables: name: "EpicAnchors" path: "/builds/$CI_PROJECT_PATH" - version: "1.3.2" + version: "1.3.3" build: stage: build diff --git a/src/main/java/com/songoda/epicanchors/EpicAnchors.java b/src/main/java/com/songoda/epicanchors/EpicAnchors.java index 3e5a4b4..b3444a9 100644 --- a/src/main/java/com/songoda/epicanchors/EpicAnchors.java +++ b/src/main/java/com/songoda/epicanchors/EpicAnchors.java @@ -9,6 +9,7 @@ import com.songoda.epicanchors.tasks.AnchorTask; import com.songoda.epicanchors.listeners.BlockListeners; import com.songoda.epicanchors.listeners.InteractListeners; import com.songoda.epicanchors.utils.*; +import com.songoda.epicanchors.utils.locale.Locale; import com.songoda.epicanchors.utils.settings.Setting; import com.songoda.epicanchors.utils.settings.SettingsManager; import com.songoda.epicanchors.utils.updateModules.LocaleModule; @@ -40,7 +41,6 @@ public class EpicAnchors extends JavaPlugin { private CommandManager commandManager; - private References references; private Locale locale; @@ -60,10 +60,8 @@ public class EpicAnchors extends JavaPlugin { this.settingsManager.setupConfig(); // Locales - String langMode = Setting.LANGUGE_MODE.getString(); - Locale.init(this); - Locale.saveDefaultLocale("en_US"); - this.locale = Locale.getLocale(langMode); + new Locale(this, "en_US"); + this.locale = Locale.getLocale(getConfig().getString("System.Language Mode")); //Running Songoda Updater Plugin plugin = new Plugin(this, 31); @@ -72,7 +70,6 @@ public class EpicAnchors extends JavaPlugin { dataFile.createNewFile("Loading Data File", "EpicAnchors Data File"); - this.references = new References(); this.anchorManager = new AnchorManager(); this.commandManager = new CommandManager(this); @@ -140,8 +137,8 @@ public class EpicAnchors extends JavaPlugin { public void reload() { + this.locale = Locale.getLocale(getConfig().getString("System.Language Mode")); this.locale.reloadMessages(); - this.references = new References(); this.loadAnchorsFromFile(); this.settingsManager.reloadConfig(); } @@ -203,8 +200,4 @@ public class EpicAnchors extends JavaPlugin { public AnchorManager getAnchorManager() { return anchorManager; } - - public References getReferences() { - return references; - } } diff --git a/src/main/java/com/songoda/epicanchors/References.java b/src/main/java/com/songoda/epicanchors/References.java deleted file mode 100644 index 5c099c4..0000000 --- a/src/main/java/com/songoda/epicanchors/References.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.songoda.epicanchors; - -public class References { - - private String prefix; - - public References() { - prefix = EpicAnchors.getInstance().getLocale().getMessage("general.nametag.prefix") + " "; - } - - public String getPrefix() { - return this.prefix; - } -} \ No newline at end of file diff --git a/src/main/java/com/songoda/epicanchors/anchor/Anchor.java b/src/main/java/com/songoda/epicanchors/anchor/Anchor.java index f4e2b11..9e0bee8 100644 --- a/src/main/java/com/songoda/epicanchors/anchor/Anchor.java +++ b/src/main/java/com/songoda/epicanchors/anchor/Anchor.java @@ -34,7 +34,7 @@ public class Anchor { if (econ.has(player, cost)) { econ.withdrawPlayer(player, cost); } else { - player.sendMessage(instance.getLocale().getMessage("event.upgrade.cannotafford")); + instance.getLocale().getMessage("event.upgrade.cannotafford").sendPrefixedMessage(player); return; } } else { @@ -48,7 +48,7 @@ public class Anchor { player.setLevel(player.getLevel() - cost); } } else { - player.sendMessage(instance.getLocale().getMessage("event.upgrade.cannotafford")); + instance.getLocale().getMessage("event.upgrade.cannotafford").sendPrefixedMessage(player); return; } } diff --git a/src/main/java/com/songoda/epicanchors/command/CommandManager.java b/src/main/java/com/songoda/epicanchors/command/CommandManager.java index 1bed880..0252397 100644 --- a/src/main/java/com/songoda/epicanchors/command/CommandManager.java +++ b/src/main/java/com/songoda/epicanchors/command/CommandManager.java @@ -52,24 +52,24 @@ public class CommandManager implements CommandExecutor { } } } - commandSender.sendMessage(instance.getReferences().getPrefix() + Methods.formatText("&7The command you entered does not exist or is spelt incorrectly.")); + instance.getLocale().newMessage("&7The command you entered does not exist or is spelt incorrectly.").sendPrefixedMessage(commandSender); return true; } private void processRequirements(AbstractCommand command, CommandSender sender, String[] strings) { if (!(sender instanceof Player) && command.isNoConsole()) { - sender.sendMessage("You must be a player to use this command."); + sender.sendMessage("You must be a player to use this commands."); return; } if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) { AbstractCommand.ReturnType returnType = command.runCommand(instance, sender, strings); if (returnType == AbstractCommand.ReturnType.SYNTAX_ERROR) { - sender.sendMessage(instance.getReferences().getPrefix() + Methods.formatText("&cInvalid Syntax!")); - sender.sendMessage(instance.getReferences().getPrefix() + Methods.formatText("&7The valid syntax is: &6" + command.getSyntax() + "&7.")); + instance.getLocale().newMessage("&cInvalid Syntax!").sendPrefixedMessage(sender); + instance.getLocale().newMessage("&7The valid syntax is: &6" + command.getSyntax() + "&7.").sendPrefixedMessage(sender); } return; } - sender.sendMessage(instance.getReferences().getPrefix() + instance.getLocale().getMessage("event.general.nopermission")); + instance.getLocale().newMessage("event.general.nopermission").sendPrefixedMessage(sender); } public List getCommands() { diff --git a/src/main/java/com/songoda/epicanchors/command/commands/CommandEpicAnchors.java b/src/main/java/com/songoda/epicanchors/command/commands/CommandEpicAnchors.java index 2a64e2e..afc7feb 100644 --- a/src/main/java/com/songoda/epicanchors/command/commands/CommandEpicAnchors.java +++ b/src/main/java/com/songoda/epicanchors/command/commands/CommandEpicAnchors.java @@ -14,7 +14,8 @@ public class CommandEpicAnchors extends AbstractCommand { @Override protected ReturnType runCommand(EpicAnchors instance, CommandSender sender, String... args) { sender.sendMessage(""); - sender.sendMessage(Methods.formatText(instance.getReferences().getPrefix() + "&7Version " + instance.getDescription().getVersion() + " Created with <3 by &5&l&oBrianna")); + instance.getLocale().newMessage("&7Version " + instance.getDescription().getVersion() + + " Created with <3 by &5&l&oSongoda").sendPrefixedMessage(sender); for (AbstractCommand command : instance.getCommandManager().getCommands()) { if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) { diff --git a/src/main/java/com/songoda/epicanchors/command/commands/CommandGive.java b/src/main/java/com/songoda/epicanchors/command/commands/CommandGive.java index a074291..ed1794b 100644 --- a/src/main/java/com/songoda/epicanchors/command/commands/CommandGive.java +++ b/src/main/java/com/songoda/epicanchors/command/commands/CommandGive.java @@ -18,7 +18,7 @@ public class CommandGive extends AbstractCommand { if (args.length != 3) return ReturnType.SYNTAX_ERROR; if (Bukkit.getPlayer(args[1]) == null && !args[1].trim().toLowerCase().equals("all")) { - sender.sendMessage(instance.getReferences().getPrefix() + "Not a player..."); + instance.getLocale().newMessage("&cThat is not a player...").sendMessage(sender); return ReturnType.SYNTAX_ERROR; } @@ -27,11 +27,11 @@ public class CommandGive extends AbstractCommand { if (!args[1].trim().toLowerCase().equals("all")) { Player player = Bukkit.getOfflinePlayer(args[1]).getPlayer(); player.getInventory().addItem(itemStack); - player.sendMessage(instance.getReferences().getPrefix() + instance.getLocale().getMessage("command.give.success")); + instance.getLocale().getMessage("command.give.success").sendPrefixedMessage(player); } else { for (Player player : Bukkit.getOnlinePlayers()) { player.getInventory().addItem(itemStack); - player.sendMessage(instance.getReferences().getPrefix() + instance.getLocale().getMessage("command.give.success")); + instance.getLocale().getMessage("command.give.success").sendPrefixedMessage(player); } } return ReturnType.SUCCESS; diff --git a/src/main/java/com/songoda/epicanchors/command/commands/CommandReload.java b/src/main/java/com/songoda/epicanchors/command/commands/CommandReload.java index ac988be..a6b6084 100644 --- a/src/main/java/com/songoda/epicanchors/command/commands/CommandReload.java +++ b/src/main/java/com/songoda/epicanchors/command/commands/CommandReload.java @@ -14,7 +14,7 @@ public class CommandReload extends AbstractCommand { @Override protected ReturnType runCommand(EpicAnchors instance, CommandSender sender, String... args) { instance.reload(); - sender.sendMessage(Methods.formatText(instance.getReferences().getPrefix() + "&7Configuration and Language files reloaded.")); + instance.getLocale().getMessage("&7Configuration and Language files reloaded.").sendPrefixedMessage(sender); return ReturnType.SUCCESS; } diff --git a/src/main/java/com/songoda/epicanchors/gui/GUIOverview.java b/src/main/java/com/songoda/epicanchors/gui/GUIOverview.java index 94a93cc..bda0b32 100644 --- a/src/main/java/com/songoda/epicanchors/gui/GUIOverview.java +++ b/src/main/java/com/songoda/epicanchors/gui/GUIOverview.java @@ -26,7 +26,7 @@ public class GUIOverview extends AbstractGUI { this.anchor = anchor; - init(Methods.formatText(plugin.getLocale().getMessage("interface.anchor.title")), 27); + init(Methods.formatText(plugin.getLocale().getMessage("interface.anchor.title").getMessage()), 27); runTask(); } @@ -58,23 +58,27 @@ public class GUIOverview extends AbstractGUI { ItemStack itemXP = new ItemStack(Material.valueOf(plugin.getConfig().getString("Interfaces.XP Icon")), 1); ItemMeta itemmetaXP = itemXP.getItemMeta(); - itemmetaXP.setDisplayName(plugin.getLocale().getMessage("interface.button.addtimewithxp")); + itemmetaXP.setDisplayName(plugin.getLocale().getMessage("interface.button.addtimewithxp").getMessage()); ArrayList loreXP = new ArrayList<>(); - loreXP.add(plugin.getLocale().getMessage("interface.button.addtimewithxplore", Integer.toString(plugin.getConfig().getInt("Main.XP Cost")))); + loreXP.add(plugin.getLocale().getMessage("interface.button.addtimewithxplore") + .processPlaceholder("cost", Integer.toString(plugin.getConfig().getInt("Main.XP Cost"))) + .getMessage()); itemmetaXP.setLore(loreXP); itemXP.setItemMeta(itemmetaXP); ItemStack itemECO = new ItemStack(Material.valueOf(plugin.getConfig().getString("Interfaces.Economy Icon")), 1); ItemMeta itemmetaECO = itemECO.getItemMeta(); - itemmetaECO.setDisplayName(plugin.getLocale().getMessage("interface.button.addtimewitheconomy")); + itemmetaECO.setDisplayName(plugin.getLocale().getMessage("interface.button.addtimewitheconomy").getMessage()); ArrayList loreECO = new ArrayList<>(); - loreECO.add(plugin.getLocale().getMessage("interface.button.addtimewitheconomylore", Methods.formatEconomy(plugin.getConfig().getInt("Main.Economy Cost")))); + loreECO.add(plugin.getLocale().getMessage("interface.button.addtimewitheconomylore") + .processPlaceholder("cost", Methods.formatEconomy(plugin.getConfig().getInt("Main.Economy Cost"))) + .getMessage()); itemmetaECO.setLore(loreECO); itemECO.setItemMeta(itemmetaECO); ItemStack item = plugin.makAnchorItem(anchor.getTicksLeft()); ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(Methods.formatText(plugin.getLocale().getMessage("interface.anchor.smalltitle"))); + meta.setDisplayName(Methods.formatText(plugin.getLocale().getMessage("interface.anchor.smalltitle").getMessage())); List lore = new ArrayList<>(); lore.add(Methods.formatText("&7" + timeRemaining)); diff --git a/src/main/java/com/songoda/epicanchors/utils/locale/Locale.java b/src/main/java/com/songoda/epicanchors/utils/locale/Locale.java new file mode 100644 index 0000000..d165c03 --- /dev/null +++ b/src/main/java/com/songoda/epicanchors/utils/locale/Locale.java @@ -0,0 +1,302 @@ +package com.songoda.epicanchors.utils.locale; + +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Assists in the utilization of localization files. + * Created to be used by the Songoda Team. + * + * @author Brianna O'Keefe - Songoda + */ +public class Locale { + + private static final List LOCALES = new ArrayList<>(); + private static final Pattern NODE_PATTERN = Pattern.compile("(\\w+(?:\\.{1}\\w+)*)\\s*=\\s*\"(.*)\""); + private static final String FILE_EXTENSION = ".lang"; + private static JavaPlugin plugin; + private static File localeFolder; + + private final Map nodes = new HashMap<>(); + + private static String defaultLocale; + + private File file; + private String name; + + /** + * Instantiate the Locale class for future use + * + * @param name the name of the instantiated language + */ + private Locale(String name) { + if (plugin == null) + return; + + this.name = name; + + String fileName = name + FILE_EXTENSION; + this.file = new File(localeFolder, fileName); + + if (!this.reloadMessages()) return; + + plugin.getLogger().info("Loaded locale \"" + fileName + "\""); + } + + /** + * Initialize the class to load all existing language files and update them. + * This must be called before any other methods in this class as otherwise + * the methods will fail to invoke + * + * @param plugin the plugin instance + * @param defaultLocale the default language + */ + public Locale(JavaPlugin plugin, String defaultLocale) { + + Locale.plugin = plugin; + Locale.localeFolder = new File(plugin.getDataFolder(), "locales/"); + + if (!localeFolder.exists()) localeFolder.mkdirs(); + + //Save the default locale file. + Locale.defaultLocale = defaultLocale; + saveLocale(defaultLocale); + + for (File file : localeFolder.listFiles()) { + String fileName = file.getName(); + if (!fileName.endsWith(FILE_EXTENSION)) continue; + + String name = fileName.substring(0, fileName.lastIndexOf('.')); + + if (name.split("_").length != 2) continue; + if (localeLoaded(name)) continue; + + LOCALES.add(new Locale(name)); + } + } + + /** + * Save a locale file from the InputStream, to the locale folder + * + * @param fileName the name of the file to save + * @return true if the operation was successful, false otherwise + */ + public static boolean saveLocale(String fileName) { + return saveLocale(plugin.getResource(defaultLocale + FILE_EXTENSION), fileName); + } + + + /** + * Save a locale file from the InputStream, to the locale folder + * + * @param in file to save + * @param fileName the name of the file to save + * @return true if the operation was successful, false otherwise + */ + public static boolean saveLocale(InputStream in, String fileName) { + if (!localeFolder.exists()) localeFolder.mkdirs(); + + if (!fileName.endsWith(FILE_EXTENSION)) + fileName = (fileName.lastIndexOf(".") == -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'))) + FILE_EXTENSION; + + File destinationFile = new File(localeFolder, fileName); + if (destinationFile.exists()) + return compareFiles(in, destinationFile); + + try (OutputStream outputStream = new FileOutputStream(destinationFile)) { + copy(in, outputStream); + + fileName = fileName.substring(0, fileName.lastIndexOf('.')); + + if (fileName.split("_").length != 2) return false; + + LOCALES.add(new Locale(fileName)); + if (defaultLocale == null) defaultLocale = fileName; + return true; + } catch (IOException e) { + return false; + } + } + + // Write new changes to existing files, if any at all + private static boolean compareFiles(InputStream in, File existingFile) { + InputStream defaultFile = + in == null ? plugin.getResource((defaultLocale != null ? defaultLocale : "en_US") + FILE_EXTENSION) : in; + + boolean changed = false; + + List defaultLines, existingLines; + try (BufferedReader defaultReader = new BufferedReader(new InputStreamReader(defaultFile)); + BufferedReader existingReader = new BufferedReader(new FileReader(existingFile)); + BufferedWriter writer = new BufferedWriter(new FileWriter(existingFile, true))) { + defaultLines = defaultReader.lines().collect(Collectors.toList()); + existingLines = existingReader.lines().map(s -> s.split("\\s*=")[0]).collect(Collectors.toList()); + + for (String defaultValue : defaultLines) { + if (defaultValue.isEmpty() || defaultValue.startsWith("#")) continue; + + String key = defaultValue.split("\\s*=")[0]; + + if (!existingLines.contains(key)) { + if (!changed) { + writer.newLine(); + writer.newLine(); + // Leave a note alerting the user of the newly added messages. + writer.write("# New messages for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + "."); + + // If changes were found outside of the default file leave a note explaining that. + if (in == null) { + writer.newLine(); + writer.write("# These translations were found untranslated, join"); + writer.newLine(); + writer.write("# our translation Discord https://discord.gg/f7fpZEf"); + writer.newLine(); + writer.write("# to request an official update!"); + } + } + + writer.newLine(); + writer.write(defaultValue); + + changed = true; + } + } + if (in != null && !changed) compareFiles(null, existingFile); + } catch (IOException e) { + return false; + } + + return changed; + } + + + /** + * Check whether a locale exists and is registered or not + * + * @param name the whole language tag (i.e. "en_US") + * @return true if it exists + */ + public static boolean localeLoaded(String name) { + for (Locale locale : LOCALES) + if (locale.getName().equals(name)) return true; + return false; + } + + + /** + * Get a locale by its entire proper name (i.e. "en_US") + * + * @param name the full name of the locale + * @return locale of the specified name + */ + public static Locale getLocale(String name) { + for (Locale locale : LOCALES) + if (locale.getName().equalsIgnoreCase(name)) return locale; + return null; + } + + /** + * Clear the previous message cache and load new messages directly from file + * + * @return reload messages from file + */ + public boolean reloadMessages() { + if (!this.file.exists()) { + plugin.getLogger().warning("Could not find file for locale \"" + this.name + "\""); + return false; + } + + this.nodes.clear(); // Clear previous data (if any) + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + for (int lineNumber = 0; (line = reader.readLine()) != null; lineNumber++) { + if (line.trim().isEmpty() || line.startsWith("#") /* Comment */) continue; + + Matcher matcher = NODE_PATTERN.matcher(line); + if (!matcher.find()) { + System.err.println("Invalid locale syntax at (line=" + lineNumber + ")"); + continue; + } + + nodes.put(matcher.group(1), matcher.group(2)); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + /** + * Supply the Message object with the plugins prefix. + * + * @param message message to be applied + * @return applied message + */ + private Message supplyPrefix(Message message) { + return message.setPrefix(this.nodes.getOrDefault("general.nametag.prefix", "[Plugin]")); + } + + /** + * Create a new unsaved Message + * + * @param message the message to create + * @return the created message + */ + public Message newMessage(String message) { + return supplyPrefix(new Message(message)); + } + + /** + * Get a message set for a specific node. + * + * @param node the node to get + * @return the message for the specified node + */ + public Message getMessage(String node) { + return this.getMessageOrDefault(node, node); + } + + /** + * Get a message set for a specific node + * + * @param node the node to get + * @param defaultValue the default value given that a value for the node was not found + * @return the message for the specified node. Default if none found + */ + public Message getMessageOrDefault(String node, String defaultValue) { + return supplyPrefix(new Message(this.nodes.getOrDefault(node, defaultValue))); + } + + /** + * Return the locale name (i.e. "en_US") + * + * @return the locale name + */ + public String getName() { + return name; + } + + private static void copy(InputStream input, OutputStream output) { + int n; + byte[] buffer = new byte[1024 * 4]; + + try { + while ((n = input.read(buffer)) != -1) { + output.write(buffer, 0, n); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/songoda/epicanchors/utils/locale/Message.java b/src/main/java/com/songoda/epicanchors/utils/locale/Message.java new file mode 100644 index 0000000..b2bdf71 --- /dev/null +++ b/src/main/java/com/songoda/epicanchors/utils/locale/Message.java @@ -0,0 +1,115 @@ +package com.songoda.epicanchors.utils.locale; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * The Message object. This holds the message to be sent + * as well as the plugins prefix so that they can both be + * easily manipulated then deployed + */ +public class Message { + + private String prefix = null; + private String message; + + /** + * create a new message + * + * @param message the message text + */ + public Message(String message) { + this.message = message; + } + + /** + * Format and send the held message to a player + * + * @param player player to send the message to + */ + public void sendMessage(Player player) { + player.sendMessage(this.getMessage()); + } + + /** + * Format and send the held message with the + * appended plugin prefix to a player + * + * @param player player to send the message to + */ + public void sendPrefixedMessage(Player player) { + player.sendMessage(this.getPrefixedMessage()); + } + + /** + * Format and send the held message to a player + * + * @param sender command sender to send the message to + */ + public void sendMessage(CommandSender sender) { + sender.sendMessage(this.getMessage()); + } + + /** + * Format and send the held message with the + * appended plugin prefix to a command sender + * + * @param sender command sender to send the message to + */ + public void sendPrefixedMessage(CommandSender sender) { + sender.sendMessage(this.getPrefixedMessage()); + } + + /** + * Format the held message and append the plugins + * prefix + * + * @return the prefixed message + */ + public String getPrefixedMessage() { + return ChatColor.translateAlternateColorCodes('&',(prefix == null ? "" : this.prefix) + + " " + this.message); + } + + /** + * Get and format the held message + * + * @return the message + */ + public String getMessage() { + return ChatColor.translateAlternateColorCodes('&', this.message); + } + + /** + * Get the held message + * + * @return the message + */ + public String getUnformattedMessage() { + return this.message; + } + + /** + * Replace the provided placeholder with the + * provided object + * + * @param placeholder the placeholder to replace + * @param replacement the replacement object + * @return the modified Message + */ + public Message processPlaceholder(String placeholder, Object replacement) { + this.message = message.replace("%" + placeholder + "%", replacement.toString()); + return this; + } + + Message setPrefix(String prefix) { + this.prefix = prefix; + return this; + } + + @Override + public String toString() { + return this.message; + } +} \ No newline at end of file diff --git a/src/main/java/com/songoda/epicanchors/utils/updateModules/LocaleModule.java b/src/main/java/com/songoda/epicanchors/utils/updateModules/LocaleModule.java index a151291..76824df 100644 --- a/src/main/java/com/songoda/epicanchors/utils/updateModules/LocaleModule.java +++ b/src/main/java/com/songoda/epicanchors/utils/updateModules/LocaleModule.java @@ -22,7 +22,7 @@ public class LocaleModule implements Module { if (file.get("type").equals("locale")) { InputStream in = new URL((String) file.get("link")).openStream(); - EpicAnchors.getInstance().getLocale().saveDefaultLocale(in, (String) file.get("name")); + EpicAnchors.getInstance().getLocale().saveLocale(in, (String) file.get("name")); } } } catch (IOException e) {