diff --git a/.gitignore b/.gitignore index ff9e624..400e349 100644 --- a/.gitignore +++ b/.gitignore @@ -1,93 +1,3 @@ -/bin/ -/.settings/ -*.project -*.classpath -.idea -target -\.idea/EpicVouchers\.iml - -\.idea/libraries/old\.xml - -\.idea/libraries/spigot_1_13_1\.xml - -\.idea/misc\.xml - -\.idea/modules\.xml - -\.idea/uiDesigner\.xml - -\.idea/vcs\.xml - -\.idea/workspace\.xml - -target/classes/com/songoda/epicvouchers/command/AbstractCommand\.class - -target/classes/com/songoda/epicvouchers/command/AbstractCommand\$ReturnType\.class - -target/classes/com/songoda/epicvouchers/command/CommandManager\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandEditor\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandEpicVouchers\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandForce\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandGive\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandList\.class - -target/classes/com/songoda/epicvouchers/command/commands/CommandReload\.class - -target/classes/com/songoda/epicvouchers/EpicVouchers\.class - -target/classes/com/songoda/epicvouchers/events/ForceRedeemEvent\.class - -target/classes/com/songoda/epicvouchers/events/VoucherReceiveEvent\.class - -target/classes/com/songoda/epicvouchers/events/VoucherRedeemEvent\.class - -target/classes/com/songoda/epicvouchers/handlers/Connections\.class - -target/classes/com/songoda/epicvouchers/handlers/PreventHacks\.class - -target/classes/com/songoda/epicvouchers/inventory/Confirmation\.class - -target/classes/com/songoda/epicvouchers/inventory/VoucherEditor\.class - -target/classes/com/songoda/epicvouchers/liberaries/Bountiful\.class - -target/classes/com/songoda/epicvouchers/Locale\.class - -target/classes/com/songoda/epicvouchers/References\.class - -target/classes/com/songoda/epicvouchers/utils/ConfigWrapper\.class - -target/classes/com/songoda/epicvouchers/utils/Debugger\.class - -target/classes/com/songoda/epicvouchers/utils/Methods\.class - -target/classes/com/songoda/epicvouchers/utils/ServerVersion\.class - -target/classes/com/songoda/epicvouchers/utils/SettingsManager\.class - -target/classes/com/songoda/epicvouchers/utils/SettingsManager\$settings\.class - -target/classes/com/songoda/epicvouchers/utils/SoundUtils\.class - -target/classes/com/songoda/epicvouchers/voucher/ClickListener\.class - -target/classes/com/songoda/epicvouchers/voucher/Cooldowns\.class - -target/classes/com/songoda/epicvouchers/voucher/Cooldowns\$1\.class - -target/classes/com/songoda/epicvouchers/voucher/Voucher\.class - -target/classes/com/songoda/epicvouchers/voucher/VoucherExecutor\.class - -target/classes/com/songoda/epicvouchers/voucher/VoucherManager\.class - -target/classes/en_US\.lang - -target/classes/plugin\.yml - -target/classes/vouchers\.yml +\.idea +\target +EpicVouchers.iml \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 78379bb..8e06c2e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ stages: variables: name: "EpicVouchers" path: "/builds/$CI_PROJECT_PATH" - version: "2.1.1" + version: "2.1.7" build: stage: build diff --git a/EpicVouchers.iml b/EpicVouchers.iml index 94f15e1..997ec0c 100644 --- a/EpicVouchers.iml +++ b/EpicVouchers.iml @@ -1,5 +1,14 @@ + + + + + SPIGOT + + + + @@ -11,6 +20,8 @@ + + diff --git a/pom.xml b/pom.xml index 442ed51..78c4975 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ maven-version-number clean install - EpicLevels-${project.version} + EpicVouchers-${project.version} org.apache.maven.plugins diff --git a/src/main/java/com/songoda/epicvouchers/EpicVouchers.java b/src/main/java/com/songoda/epicvouchers/EpicVouchers.java index 27688a9..4dfc0c8 100644 --- a/src/main/java/com/songoda/epicvouchers/EpicVouchers.java +++ b/src/main/java/com/songoda/epicvouchers/EpicVouchers.java @@ -11,6 +11,7 @@ import com.songoda.epicvouchers.utils.ConfigWrapper; import com.songoda.epicvouchers.utils.Methods; import com.songoda.epicvouchers.utils.ServerVersion; import com.songoda.epicvouchers.utils.SettingsManager; +import com.songoda.epicvouchers.utils.locale.Locale; import com.songoda.epicvouchers.utils.updateModules.LocaleModule; import com.songoda.epicvouchers.voucher.CoolDownManager; import com.songoda.epicvouchers.voucher.Voucher; @@ -52,10 +53,9 @@ public class EpicVouchers extends JavaPlugin { Bukkit.getConsoleSender().sendMessage(Methods.format("&7EpicVouchers " + this.getDescription().getVersion() + " by &5Songoda <3&7!")); Bukkit.getConsoleSender().sendMessage(Methods.format("&7Action: &aEnabling&7...")); - // Locales - Locale.init(this); - Locale.saveDefaultLocale("en_US"); - this.locale = Locale.getLocale(getConfig().getString("Locale", "en_US")); + // Setup language + new Locale(this, "en_US"); + this.locale = Locale.getLocale(getConfig().getString("System.Language Mode")); //Running Songoda Updater Plugin plugin = new Plugin(this, 25); @@ -149,7 +149,8 @@ public class EpicVouchers extends JavaPlugin { loadVouchersFromFile(); reloadConfig(); saveConfig(); - locale.reloadMessages(); + this.locale = Locale.getLocale(getConfig().getString("System.Language Mode")); + this.locale.reloadMessages(); } @Override diff --git a/src/main/java/com/songoda/epicvouchers/Locale.java b/src/main/java/com/songoda/epicvouchers/Locale.java deleted file mode 100644 index 5cbb6aa..0000000 --- a/src/main/java/com/songoda/epicvouchers/Locale.java +++ /dev/null @@ -1,375 +0,0 @@ -package com.songoda.epicvouchers; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import org.bukkit.ChatColor; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.*; -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 creation of multiple localizations and languages, - * as well as the generation of default .lang files - * - * @author Parker Hawke - 2008Choco - */ -public class Locale { - - private static final List LOCALES = Lists.newArrayList(); - 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 static String defaultLocale; - - private final Map nodes = new HashMap<>(); - - private final File file; - private final String name, region; - - private Locale(String name, String region) { - if (plugin == null) - throw new IllegalStateException("Cannot generate locales without first initializing the class (Locale#init(JavaPlugin))"); - - this.name = name.toLowerCase(); - this.region = region.toUpperCase(); - - String fileName = name + "_" + region + FILE_EXTENSION; - this.file = new File(localeFolder, fileName); - - if (this.reloadMessages()) return; - - plugin.getLogger().info("Loaded locale " + fileName); - } - - /** - * Initialize the locale class to generate information and search for localizations. - * This must be called before any other methods in the Locale class can be invoked. - * Note that this will also call {@link #searchForLocales()}, so there is no need to - * invoke it for yourself after the initialization - * - * @param plugin the plugin instance - */ - public static void init(JavaPlugin plugin) { - Locale.plugin = plugin; - - if (localeFolder == null) { - localeFolder = new File(plugin.getDataFolder(), "locales/"); - } - - localeFolder.mkdirs(); - Locale.searchForLocales(); - } - - /** - * Find all .lang file locales under the "locales" folder - */ - public static void searchForLocales() { - if (!localeFolder.exists()) localeFolder.mkdirs(); - - for (File file : localeFolder.listFiles()) { - String name = file.getName(); - if (!name.endsWith(".lang")) continue; - - String fileName = name.substring(0, name.lastIndexOf('.')); - String[] localeValues = fileName.split("_"); - - if (localeValues.length != 2) continue; - if (localeExists(localeValues[0] + "_" + localeValues[1])) continue; - - LOCALES.add(new Locale(localeValues[0], localeValues[1])); - plugin.getLogger().info("Found and loaded locale \"" + fileName + "\""); - } - } - - /** - * 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.getLanguageTag().equalsIgnoreCase(name)) return locale; - return null; - } - - /** - * Get a locale from the cache by its name (i.e. "en" from "en_US") - * - * @param name the name of the language - * @return locale of the specified language. Null if not cached - */ - public static Locale getLocaleByName(String name) { - for (Locale locale : LOCALES) - if (locale.getName().equalsIgnoreCase(name)) return locale; - return null; - } - - /** - * Get a locale from the cache by its region (i.e. "US" from "en_US") - * - * @param region the name of the region - * @return locale of the specified region. Null if not cached - */ - public static Locale getLocaleByRegion(String region) { - for (Locale locale : LOCALES) - if (locale.getRegion().equalsIgnoreCase(region)) return locale; - return null; - } - - /** - * 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 localeExists(String name) { - for (Locale locale : LOCALES) - if (locale.getLanguageTag().equals(name)) return true; - return false; - } - - /** - * Get an immutable list of all currently loaded locales - * - * @return list of all locales - */ - public static List getLocales() { - return ImmutableList.copyOf(LOCALES); - } - - /** - * Save a default locale file from the project source directory, 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 saveDefaultLocale(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(plugin.getResource(fileName), destinationFile); - } - - try (OutputStream outputStream = new FileOutputStream(destinationFile)) { - copy(in == null ? plugin.getResource(fileName) : in, outputStream); - - fileName = fileName.substring(0, fileName.lastIndexOf('.')); - String[] localeValues = fileName.split("_"); - - if (localeValues.length != 2) return false; - - LOCALES.add(new Locale(localeValues[0], localeValues[1])); - if (defaultLocale == null) defaultLocale = fileName; - - return true; - } catch (IOException e) { - return false; - } - } - - /** - * Save a default locale file from the project source directory, 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 saveDefaultLocale(String fileName) { - return saveDefaultLocale(null, fileName); - } - - /** - * Clear all current locale data - */ - public static void clearLocaleData() { - for (Locale locale : LOCALES) - locale.nodes.clear(); - LOCALES.clear(); - } - - // Write new changes to existing files, if any at all - private static boolean compareFiles(InputStream defaultFile, File existingFile) { - // Look for default - if (defaultFile == null) { - defaultFile = plugin.getResource(defaultLocale != null ? defaultLocale : "en_US"); - if (defaultFile == null) return false; // No default at all - } - - 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(); - writer.write("# New messages for " + plugin.getName() + " v" + plugin.getDescription().getVersion()); - } - - writer.newLine(); - writer.write(defaultValue); - - changed = true; - } - } - } catch (IOException e) { - return false; - } - - return changed; - } - - 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(); - } - } - - /** - * Get the name of the language that this locale is based on. - * (i.e. "en" for English, or "fr" for French) - * - * @return the name of the language - */ - public String getName() { - return name; - } - - /** - * Get the name of the region that this locale is from. - * (i.e. "US" for United States or "CA" for Canada) - * - * @return the name of the region - */ - public String getRegion() { - return region; - } - - /** - * Return the entire locale tag (i.e. "en_US") - * - * @return the language tag - */ - public String getLanguageTag() { - return name + "_" + region; - } - - /** - * Get the file that represents this locale - * - * @return the locale file (.lang) - */ - public File getFile() { - return file; - } - - /** - * Get a message set for a specific node - * - * @param node the node to get - * @return the message for the specified node - */ - public String getMessage(String node) { - return ChatColor.translateAlternateColorCodes('&', this.getMessageOrDefault(node, node)); - } - - /** - * Get a message set for a specific node and replace its params with a supplied arguments. - * - * @param node the node to get - * @param args the replacement arguments - * @return the message for the specified node - */ - public String getMessage(String node, Object... args) { - String message = getMessage(node); - for (Object arg : args) { - message = message.replaceFirst("\\%.*?\\%", arg.toString()); - } - return message; - } - - /** - * 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 String getMessageOrDefault(String node, String defaultValue) { - return this.nodes.getOrDefault(node, defaultValue); - } - - /** - * Get the key-value map of nodes to messages - * - * @return node-message map - */ - public Map getMessageNodeMap() { - return ImmutableMap.copyOf(nodes); - } - - /** - * 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.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; - } - -} \ No newline at end of file diff --git a/src/main/java/com/songoda/epicvouchers/command/EfCommand.java b/src/main/java/com/songoda/epicvouchers/command/EfCommand.java index ce8ae0a..b7f5344 100644 --- a/src/main/java/com/songoda/epicvouchers/command/EfCommand.java +++ b/src/main/java/com/songoda/epicvouchers/command/EfCommand.java @@ -24,15 +24,16 @@ public class EfCommand extends BaseCommand { @Description("Reload all configuration files.") public void onReload(CommandSender sender) { instance.reload(); - sender.sendMessage(instance.getLocale().getMessage("command.reload.success")); + instance.getLocale().getMessage("command.reload.success").sendPrefixedMessage(sender); } @Subcommand("list") @CommandPermission("epicvouchers.admin") @Description("List all available vouchers.") public void onList(CommandSender sender) { - String message = instance.getLocale().getMessage("command.list.list").replaceAll("%list%", String.join(", ", instance.getVouchers().keySet())); - sender.sendMessage(message); + instance.getLocale().getMessage("command.list.list") + .processPlaceholder("%list%", String.join(", ", instance.getVouchers().keySet())) + .sendPrefixedMessage(sender); } @Subcommand("give") @@ -57,7 +58,11 @@ public class EfCommand extends BaseCommand { @Description("Force user to redeem voucher.") public void onForce(CommandSender sender, @Flags("other") Player player, Voucher voucher, int amount) { voucher.forceRedeem(sender, Collections.singletonList(player), amount); - sender.sendMessage(instance.getLocale().getMessage("command.force.send", player.getName(), voucher.getName(true), String.valueOf(amount))); + instance.getLocale().getMessage("command.force.send") + .processPlaceholder("player", player.getName()) + .processPlaceholder("voucher", voucher.getName(true)) + .processPlaceholder("amount", String.valueOf(amount)) + .sendPrefixedMessage(sender); } @Subcommand("forceall") @@ -66,7 +71,11 @@ public class EfCommand extends BaseCommand { @Description("Force all online users to redeem voucher.") public void onForceAll(CommandSender sender, Voucher voucher, int amount) { voucher.forceRedeem(sender, new ArrayList<>(Bukkit.getOnlinePlayers()), amount); - sender.sendMessage(instance.getLocale().getMessage("command.force.send", "everyone", voucher.getName(true), String.valueOf(amount))); + instance.getLocale().getMessage("command.force.send") + .processPlaceholder("player", "everyone") + .processPlaceholder("voucher", voucher.getName(true)) + .processPlaceholder("amount", String.valueOf(amount)) + .sendPrefixedMessage(sender); } @Subcommand("editor") diff --git a/src/main/java/com/songoda/epicvouchers/libraries/inventory/icons/IntegerIcon.java b/src/main/java/com/songoda/epicvouchers/libraries/inventory/icons/IntegerIcon.java index b8bb2a0..839033f 100644 --- a/src/main/java/com/songoda/epicvouchers/libraries/inventory/icons/IntegerIcon.java +++ b/src/main/java/com/songoda/epicvouchers/libraries/inventory/icons/IntegerIcon.java @@ -9,6 +9,6 @@ import java.util.function.BiConsumer; public class IntegerIcon extends StringIcon { public IntegerIcon(EpicVouchers instance, String string, int current, BiConsumer consumer) { - super(instance, Material.IRON_INGOT, string, "" + current, (player, edited) -> consumer.accept(player, Integer.parseInt(edited)), StringUtils::isNumeric); + super(instance, Material.IRON_INGOT, string, String.valueOf(current), (player, edited) -> consumer.accept(player, edited.equals("") ? 0 : Integer.parseInt(edited)), StringUtils::isNumeric); } } diff --git a/src/main/java/com/songoda/epicvouchers/listeners/PlayerInteractListener.java b/src/main/java/com/songoda/epicvouchers/listeners/PlayerInteractListener.java index 772a25e..28d2170 100644 --- a/src/main/java/com/songoda/epicvouchers/listeners/PlayerInteractListener.java +++ b/src/main/java/com/songoda/epicvouchers/listeners/PlayerInteractListener.java @@ -23,44 +23,44 @@ public class PlayerInteractListener implements Listener { @EventHandler public void voucherListener(PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK || !event.hasItem()) { + final ItemStack item = event.getItem(); + if (item == null || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK)) return; - } + final Player player = event.getPlayer(); for (Voucher voucher : instance.getVouchers().values()) { - Player player = event.getPlayer(); - if (!player.hasPermission(voucher.getPermission())) { + final ItemStack voucherItem = voucher.getItemStack(); + + // does the item they're holding match this voucher? + if (voucherItem != null && !voucher.getItemStack().isSimilar(item)) continue; + else if (item.getType() != voucher.getMaterial() || item.getDurability() != voucher.getData()) + continue; + else { + // material matches - verify the name + lore + final ItemMeta meta = item.getItemMeta(); + if (meta == null || !meta.hasDisplayName() || !meta.hasLore() || !meta.getDisplayName().equals(voucher.getName(true)) || !meta.getLore().equals(voucher.getLore(true))) + continue; } - ItemStack item = event.getItem(); + event.setCancelled(true); - if (voucher.getItemStack() != null) { - if (!voucher.getItemStack().isSimilar(item)) { - continue; - } - } else { - if (item.getType() != voucher.getMaterial() || item.getDurability() != voucher.getData()) { - continue; - } - - ItemMeta meta = item.getItemMeta(); - - if (!item.hasItemMeta() || !meta.hasDisplayName() || !meta.getDisplayName().equals(voucher.getName(true)) || !meta.getLore().equals(voucher.getLore(true))) { - continue; - } + // does the player have permission to redeem this voucher? + if (!voucher.getPermission().isEmpty() && !player.hasPermission(voucher.getPermission())) { + // todo: probably should send a message to the player... + return; } - UUID uuid = player.getUniqueId(); if (instance.getCoolDowns().isOnCoolDown(uuid)) { - player.sendMessage(instance.getLocale().getMessage("event.general.cooldown", instance.getCoolDowns().getTime(uuid), voucher.getName(true))); + instance.getLocale().getMessage("event.general.cooldown") + .processPlaceholder("time", instance.getCoolDowns().getTime(uuid)) + .processPlaceholder("voucher", voucher.getName(true)) + .sendPrefixedMessage(player); return; } - event.setCancelled(true); - if (voucher.isConfirm()) { new ConfirmMenu(instance, () -> instance.getVoucherExecutor().redeemVoucher(player, voucher, item, true, event), diff --git a/src/main/java/com/songoda/epicvouchers/menus/ConfirmMenu.java b/src/main/java/com/songoda/epicvouchers/menus/ConfirmMenu.java index 7ad2b17..d93d173 100644 --- a/src/main/java/com/songoda/epicvouchers/menus/ConfirmMenu.java +++ b/src/main/java/com/songoda/epicvouchers/menus/ConfirmMenu.java @@ -13,19 +13,19 @@ import static org.bukkit.inventory.ItemFlag.HIDE_ENCHANTS; public class ConfirmMenu extends FastInv { public ConfirmMenu(EpicVouchers instance, Runnable success, Runnable failure) { - super(27, instance.getLocale().getMessage("interface.confirmsettings.title")); + super(27, instance.getLocale().getMessage("interface.confirmsettings.title").getMessage()); addItem(11, new ItemBuilder(Material.EMERALD) - .name(instance.getLocale().getMessage("interface.confirmsettings.confirmitemname")) - .lore(instance.getLocale().getMessage("interface.confirmsettings.confirmitemlore")) + .name(instance.getLocale().getMessage("interface.confirmsettings.confirmitemname").getMessage()) + .lore(instance.getLocale().getMessage("interface.confirmsettings.confirmitemlore").getMessage()) .addGlow().build(), event -> { event.getPlayer().closeInventory(); success.run(); }); addItem(15, new ItemBuilder(Material.REDSTONE_BLOCK) - .name(instance.getLocale().getMessage("interface.confirmsettings.cancelitemname")) - .lore(instance.getLocale().getMessage("interface.confirmsettings.cancelitemlore")) + .name(instance.getLocale().getMessage("interface.confirmsettings.cancelitemname").getMessage()) + .lore(instance.getLocale().getMessage("interface.confirmsettings.cancelitemlore").getMessage()) .enchant(DURABILITY, 1) .addFlags(HIDE_ENCHANTS) .build(), event -> { diff --git a/src/main/java/com/songoda/epicvouchers/utils/locale/Locale.java b/src/main/java/com/songoda/epicvouchers/utils/locale/Locale.java new file mode 100644 index 0000000..9af1a66 --- /dev/null +++ b/src/main/java/com/songoda/epicvouchers/utils/locale/Locale.java @@ -0,0 +1,302 @@ +package com.songoda.epicvouchers.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/epicvouchers/utils/locale/Message.java b/src/main/java/com/songoda/epicvouchers/utils/locale/Message.java new file mode 100644 index 0000000..d6b58ff --- /dev/null +++ b/src/main/java/com/songoda/epicvouchers/utils/locale/Message.java @@ -0,0 +1,115 @@ +package com.songoda.epicvouchers.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/epicvouchers/utils/updateModules/LocaleModule.java b/src/main/java/com/songoda/epicvouchers/utils/updateModules/LocaleModule.java index b21aae5..e34168e 100644 --- a/src/main/java/com/songoda/epicvouchers/utils/updateModules/LocaleModule.java +++ b/src/main/java/com/songoda/epicvouchers/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(); - EpicVouchers.getInstance().getLocale().saveDefaultLocale(in, (String) file.get("name")); + EpicVouchers.getInstance().getLocale().saveLocale(in, (String) file.get("name")); } } } catch (IOException e) { diff --git a/src/main/java/com/songoda/epicvouchers/voucher/Voucher.java b/src/main/java/com/songoda/epicvouchers/voucher/Voucher.java index 47675bf..ff97356 100644 --- a/src/main/java/com/songoda/epicvouchers/voucher/Voucher.java +++ b/src/main/java/com/songoda/epicvouchers/voucher/Voucher.java @@ -141,22 +141,23 @@ public class Voucher { } public void give(CommandSender sender, List players, int amount) { + String giveMessage = instance.getLocale().getMessage("command.give.send") - .replaceAll("%player%", players.size() == 1 ? players.get(0).getName() : "everyone") - .replaceAll("%voucher%", Matcher.quoteReplacement(getName(true))) - .replaceAll("%amount%", String.valueOf(amount)); + .processPlaceholder("%player%", players.size() == 1 ? players.get(0).getName() : "everyone") + .processPlaceholder("%voucher%", Matcher.quoteReplacement(getName(true))) + .processPlaceholder("%amount%", String.valueOf(amount)).getPrefixedMessage(); for (Player player : players) { String receiveMessage = instance.getLocale().getMessage("command.give.receive") - .replaceAll("%voucher%", getName(true)) - .replaceAll("%player%", player.getName()) - .replaceAll("%amount%", String.valueOf(amount)); + .processPlaceholder("%voucher%", Matcher.quoteReplacement(getName(true))) + .processPlaceholder("%player%", player.getName()) + .processPlaceholder("%amount%", String.valueOf(amount)).getPrefixedMessage(); VoucherReceiveEvent event = new VoucherReceiveEvent(player, getName(true), toItemStack(amount), amount, sender); Bukkit.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { - sender.sendMessage(instance.getLocale().getMessage("command.give.cancelled")); + instance.getLocale().getMessage("command.give.cancelled").sendPrefixedMessage(sender); continue; } diff --git a/src/main/resources/en_US.lang b/src/main/resources/en_US.lang index 3f56ba2..1b0cfad 100644 --- a/src/main/resources/en_US.lang +++ b/src/main/resources/en_US.lang @@ -1,29 +1,29 @@ #General Messages -general.nametag.prefix= "&8[&6EpicVouchers&8] " +general.nametag.prefix = "&8[&6EpicVouchers&8] " #Interface Messages -interface.confirmsettings.title= "&6Confirmation" -interface.confirmsettings.confirmitemname= "&2&lCONFIRM" -interface.confirmsettings.confirmitemlore= "&aClick here if you want to confirm your action." -interface.confirmsettings.cancelitemname= "&4&lCANCEL" -interface.confirmsettings.cancelitemlore= "&cClick here if you want to cancel your action." +interface.confirmsettings.title = "&6Confirmation" +interface.confirmsettings.confirmitemname = "&2&lCONFIRM" +interface.confirmsettings.confirmitemlore = "&aClick here if you want to confirm your action." +interface.confirmsettings.cancelitemname = "&4&lCANCEL" +interface.confirmsettings.cancelitemlore = "&cClick here if you want to cancel your action." #Command Messages -command.error.noplayer= "&7That user either does not exist or is not online" -command.error.novoucher= "&cThat voucher does not exist." -command.error.notexist= "&7The command you entered does not exist or is spelt incorrectly." -command.error.notnumber= "&7Failed to parse that number into a valid amount." -command.reload.success= "&7Reloaded all config files and vouchers." -command.give.send= "&7You have given &6%player% &7the voucher &6%voucher% &7(&6x%amount%&7)." -command.give.receive= "&7You have received the voucher &6%voucher% &7(&6x%amount%&7)." -command.give.cancelled= "&cEvent got cancelled." -command.force.send= "&7You have forced &6%player% &7to redeem the voucher &6%voucher% &7(&6x%amount%&7)." -command.list.list= "&7List of all vouchers: &6%list%&7." +command.error.noplayer = "&7That user either does not exist or is not online" +command.error.novoucher = "&cThat voucher does not exist." +command.error.notexist = "&7The command you entered does not exist or is spelt incorrectly." +command.error.notnumber = "&7Failed to parse that number into a valid amount." +command.reload.success = "&7Reloaded all config files and vouchers." +command.give.send = "&7You have given &6%player% &7the voucher &6%voucher% &7(&6x%amount%&7)." +command.give.receive = "&7You have received the voucher &6%voucher% &7(&6x%amount%&7)." +command.give.cancelled = "&cEvent got cancelled." +command.force.send = "&7You have forced &6%player% &7to redeem the voucher &6%voucher% &7(&6x%amount%&7)." +command.list.list = "&7List of all vouchers: &6%list%&7." #Event Messages -event.general.nopermission= "&cYou do not have permission to do that." -event.general.cooldown= "&7Please wait &6%time%&7 seconds before redeeming a new voucher." \ No newline at end of file +event.general.nopermission = "&cYou do not have permission to do that." +event.general.cooldown = "&7Please wait &6%time%&7 seconds before redeeming a new voucher." \ No newline at end of file