Implemented locales that include addons.

This is a first working version and can probably be improved.

Firstly, the plugin will save any BSkyBlock language files to the locale
folder from the BSkyBlock jar if and only if the locale folder does not
exist. It will then do the same for any addons as they are loaded. Addon
language files are prefixed with their addon name to keep them separate
and recongnizable.

Then the plugin loads the language files and merges common languages
together into a YAMLConfiguration that is held in memory. The combined
config is never saved out to the file system.

If a request is made for a particular reference to a language that does
not exist or if the reference does not exist, then the default language
is tried.
This commit is contained in:
Tastybento 2018-01-01 18:32:59 -08:00
parent 32794a5d3f
commit 30fb087777
14 changed files with 178 additions and 237 deletions

View File

@ -1,13 +1,10 @@
package us.tastybento.bskyblock;
import java.io.File;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import us.tastybento.bskyblock.api.BSBModule;
import us.tastybento.bskyblock.commands.AdminCommand;
import us.tastybento.bskyblock.commands.IslandCommand;
import us.tastybento.bskyblock.config.PluginConfig;
@ -28,7 +25,7 @@ import us.tastybento.bskyblock.managers.LocalesManager;
* @author Tastybento
* @author Poslovitch
*/
public class BSkyBlock extends JavaPlugin implements BSBModule {
public class BSkyBlock extends JavaPlugin {
private static BSkyBlock plugin;
@ -98,7 +95,7 @@ public class BSkyBlock extends JavaPlugin implements BSBModule {
Settings.defaultLanguage = "en-US";
localesManager = new LocalesManager(plugin);
localesManager.registerLocales(plugin);
//TODO localesManager.registerLocales(plugin);
// Register Listeners
registerListeners();
@ -107,7 +104,7 @@ public class BSkyBlock extends JavaPlugin implements BSBModule {
flagsManager = new FlagsManager();
// Load addons
addonsManager = new AddonsManager();
addonsManager = new AddonsManager(plugin);
addonsManager.enableAddons();
/*
@ -237,18 +234,4 @@ public class BSkyBlock extends JavaPlugin implements BSBModule {
return flagsManager;
}
@Override
public final String getIdentifier() {
return getDescription().getName();
}
@Override
public final boolean isAddon() {
return false;
}
@Override
public final File getFolder() {
return getDataFolder();
}
}

View File

@ -1,10 +0,0 @@
package us.tastybento.bskyblock.api;
import java.io.File;
public interface BSBModule {
String getIdentifier();
boolean isAddon();
File getFolder();
}

View File

@ -134,23 +134,24 @@ public abstract class Addon implements AddonInterface {
saveResource(ADDON_CONFIG_FILENAME, false);
config = loadYamlFile(ADDON_CONFIG_FILENAME);
}
/**
* Saves a resource contained in this add-on's jar file to the addon's data folder.
* @param resourcePath in jar file
* @param replace - if true, will overwrite previous file
*/
public void saveResource(String resourcePath, boolean replace) {
saveResource(resourcePath, dataFolder, replace);
saveResource(resourcePath, dataFolder, replace, false);
}
/**
* Saves a resource contained in this add-on's jar file to the destination folder.
* @param resourcePath in jar file
* @param destinationFolder on file system
* @param replace - if true, will overwrite previous file
* @param prefix - if true, filename will be prefixed with the name of this addon
*/
public void saveResource(String resourcePath, File destinationFolder, boolean replace) {
public void saveResource(String resourcePath, File destinationFolder, boolean replace, boolean prefix) {
if (resourcePath == null || resourcePath.equals("")) {
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
}
@ -168,9 +169,15 @@ public abstract class Addon implements AddonInterface {
throw new IllegalArgumentException("The embedded resource '" + resourcePath + "' cannot be found in " + jar.getName());
}
File outFile = new File(destinationFolder, resourcePath);
//Bukkit.getLogger().info("DEBUG: outFile = " + outFile.getAbsolutePath());
//Bukkit.getLogger().info("DEBUG: outFile name = " + outFile.getName());
if (prefix) {
// Rename with addon prefix
outFile = new File(outFile.getParent(), getDescription().getName() + "-" + outFile.getName());
}
int lastIndex = resourcePath.lastIndexOf('/');
File outDir = new File(destinationFolder, resourcePath.substring(0, lastIndex >= 0 ? lastIndex : 0));
//Bukkit.getLogger().info("DEBUG: outDir = " + outDir.getAbsolutePath());
if (!outDir.exists()) {
outDir.mkdirs();
}
@ -215,7 +222,7 @@ public abstract class Addon implements AddonInterface {
public void setDescription(AddonDescription desc){
this.description = desc;
}
/**
* Set whether this addon is enabled or not
* @param enabled

View File

@ -100,7 +100,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
this.subCommands = new LinkedHashMap<>();
// Register command if it is not already registered
if (getPlugin().getCommand(label) == null) {
getPlugin().getCommandsManager().registerCommand(getPlugin(), this);
getPlugin().getCommandsManager().registerCommand(this);
}
this.setup();
if (!this.getSubCommand("help").isPresent() && !label.equals("help"))

View File

@ -1,6 +1,7 @@
package us.tastybento.bskyblock.api.commands;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -16,6 +17,7 @@ import org.bukkit.inventory.PlayerInventory;
import org.bukkit.permissions.PermissionAttachmentInfo;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.config.Settings;
/**
* BSB's user object. Wraps Player.
@ -73,7 +75,7 @@ public class User {
private final Player player;
private final UUID playerUUID;
private final BSkyBlock plugin = BSkyBlock.getInstance();
private final CommandSender sender;
@ -90,7 +92,7 @@ public class User {
this.playerUUID = player.getUniqueId();
users.put(player.getUniqueId(), this);
}
private User(UUID playerUUID) {
this.player = Bukkit.getPlayer(playerUUID);
this.playerUUID = playerUUID;
@ -155,7 +157,7 @@ public class User {
* @return
*/
public String getTranslation(String reference, String... variables) {
String translation = plugin.getLocalesManager().get(sender, reference);
String translation = plugin.getLocalesManager().get(this, reference);
if (variables.length > 1) {
for (int i = 0; i < variables.length; i+=2) {
translation.replace(variables[i], variables[i+1]);
@ -163,7 +165,7 @@ public class User {
}
return translation;
}
/**
* Send a message to sender if message is not empty. Does not include color codes or spaces.
* @param reference - language file reference
@ -210,7 +212,7 @@ public class User {
public void teleport(Location location) {
player.teleport(location);
}
/**
* Gets the current world this entity resides in
* @return World
@ -218,11 +220,24 @@ public class User {
public World getWorld() {
return player.getWorld();
}
/**
* Closes the user's inventory
*/
public void closeInventory() {
player.closeInventory();
}
/**
* Get the user's locale
* @return Locale
*/
public Locale getLocale() {
if (sender instanceof Player) {
if (!plugin.getPlayers().getLocale(this.playerUUID).isEmpty())
return Locale.forLanguageTag(plugin.getPlayers().getLocale(this.playerUUID));
}
return Locale.forLanguageTag(Settings.defaultLanguage);
}
}

View File

@ -17,8 +17,8 @@ public class BSBLocale {
private YamlConfiguration config;
private Map<String, String> cache;
public BSBLocale(String languageTag, File file) {
this.locale = Locale.forLanguageTag(languageTag);
public BSBLocale(Locale locale, File file) {
this.locale = locale;
this.config = YamlConfiguration.loadConfiguration(file);
this.cache = new HashMap<>();
}
@ -70,4 +70,22 @@ public class BSBLocale {
return this.locale.toLanguageTag();
}
/**
* Adds language YAML file to this locale
* @param language
*/
public void add(File language) {
YamlConfiguration toBeMerged = YamlConfiguration.loadConfiguration(language);
for (String key : toBeMerged.getKeys(true)) {
if (!config.contains(key)) {
//Bukkit.getLogger().info("Merging in key " + key );
config.set(key, toBeMerged.get(key));
}
}
}
public boolean contains(String reference) {
return config.contains(reference);
}
}

View File

@ -5,6 +5,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -33,10 +34,12 @@ public final class AddonsManager {
private List<Addon> addons;
private List<AddonClassLoader> loader;
private final Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
private BSkyBlock plugin;
public AddonsManager() {
public AddonsManager(BSkyBlock plugin) {
this.plugin = plugin;
this.addons = new ArrayList<>();
this.loader = new ArrayList<>();
}
@ -46,7 +49,7 @@ public final class AddonsManager {
* @throws InvalidDescriptionException
*/
public void enableAddons() {
File f = new File(BSkyBlock.getInstance().getDataFolder(), "addons");
File f = new File(plugin.getDataFolder(), "addons");
if (f.exists()) {
if (f.isDirectory()) {
for (File file : f.listFiles()) {
@ -78,7 +81,7 @@ public final class AddonsManager {
addon.onEnable();
Bukkit.getPluginManager().callEvent(AddonEvent.builder().addon(addon).reason(AddonEvent.Reason.ENABLE).build());
addon.setEnabled(true);
BSkyBlock.getInstance().getLogger().info("Enabling " + addon.getDescription().getName() + "...");
plugin.getLogger().info("Enabling " + addon.getDescription().getName() + "...");
});
}
@ -123,12 +126,19 @@ public final class AddonsManager {
// Add to the list of loaders
this.loader.add(loader);
// Get the addon itseld
// Get the addon itself
addon = loader.addon;
// Initialize some settings
addon.setDataFolder(new File(f.getParent(), addon.getDescription().getName()));
addon.setAddonFile(f);
// Obtain any locale files and save them
for (String localeFile : listJarYamlFiles(jar, "locales")) {
//plugin.getLogger().info("DEBUG: saving " + localeFile + " from jar");
addon.saveResource(localeFile, plugin.getDataFolder(), false, true);
}
plugin.getLocalesManager().loadLocales(addon.getDescription().getName());
// Fire the load event
Bukkit.getPluginManager().callEvent(AddonEvent.builder().addon(addon).reason(AddonEvent.Reason.LOAD).build());
@ -139,13 +149,13 @@ public final class AddonsManager {
addon.onLoad();
// Inform the console
BSkyBlock.getInstance().getLogger().info("Loading BSkyBlock addon " + addon.getDescription().getName() + "...");
plugin.getLogger().info("Loading BSkyBlock addon " + addon.getDescription().getName() + "...");
// Close the jar
jar.close();
} catch (IOException e) {
if (DEBUG) {
BSkyBlock.getInstance().getLogger().info(f.getName() + "is not a jarfile, ignoring...");
plugin.getLogger().info(f.getName() + "is not a jarfile, ignoring...");
}
}
@ -239,4 +249,37 @@ public final class AddonsManager {
}
}
/**
* Lists all the yml files found in the jar in the folder
* @param jar
* @param folderPath
* @return List<String>
* @throws IOException
*/
public List<String> listJarYamlFiles(JarFile jar, String folderPath) throws IOException {
List<String> result = new ArrayList<>();
/**
* Loop through all the entries.
*/
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String path = entry.getName();
/**
* Not in the folder.
*/
if (!path.startsWith(folderPath)) {
continue;
}
//plugin.getLogger().info("DEBUG: jar filename = " + entry.getName());
if (entry.getName().endsWith(".yml")) {
result.add(entry.getName());
}
}
return result;
}
}

View File

@ -1,32 +1,21 @@
package us.tastybento.bskyblock.managers;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import us.tastybento.bskyblock.api.BSBModule;
public final class CommandsManager {
private static final boolean DEBUG = false;
private Map<BSBModule, List<Command>> commands = new LinkedHashMap<>();
private HashMap<String, Command> commands = new HashMap<>();
public void registerCommand(BSBModule module, Command command) {
public void registerCommand(Command command) {
if (DEBUG)
Bukkit.getLogger().info("DEBUG: registering command for " + module.getIdentifier() + " - " + command.getLabel());
List<Command> cmds = new ArrayList<>();
if (commands.containsKey(module)) {
cmds = commands.get(module);
}
cmds.add(command);
commands.put(module, cmds);
Bukkit.getLogger().info("DEBUG: registering command - " + command.getLabel());
commands.put(command.getLabel(), command);
// Use reflection to obtain the commandMap method in Bukkit's server. It used to be visible, but isn't anymore.
try{
Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
@ -39,31 +28,10 @@ public final class CommandsManager {
}
}
public void unregisterCommand(Command command) {
// TODO - is this ever going to be used?
public Command getCommand(String command) {
return commands.get(command);
}
/**
* Get all of the commands for this
* @param module
* @return list of commands
*/
public List<Command> getCommands(BSBModule module) {
return commands.get(module);
}
/**
* Get the command with the label
* @param label
* @return the command or null if it is not there
*/
public Command getCommand(String label) {
for (List<Command> cmds : commands.values()) {
for (Command cmd : cmds) {
if (cmd.getLabel().equals(label) || cmd.getAliases().contains(label)) return cmd;
}
}
return null;
}
}

View File

@ -1,172 +1,89 @@
package us.tastybento.bskyblock.managers;
import java.io.File;
import java.io.FilenameFilter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Locale;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.api.BSBModule;
import us.tastybento.bskyblock.api.commands.User;
import us.tastybento.bskyblock.api.localization.BSBLocale;
import us.tastybento.bskyblock.config.Settings;
import us.tastybento.bskyblock.util.FileLister;
/**
* @author Poslovitch
*/
public final class LocalesManager {
public class LocalesManager {
private BSkyBlock plugin;
private Map<BSBModule, List<BSBLocale>> locales;
private HashMap<Locale, BSBLocale> languages = new HashMap<>();
final static String LOCALE_FOLDER = "locales";
private static final boolean DEBUG = false;
public LocalesManager(BSkyBlock plugin) {
this.plugin = plugin;
this.locales = new HashMap<>();
this.loadLocales("BSB"); // Default
}
public String get(User user, String reference) {
BSBLocale locale = languages.get(user.getLocale());
if (locale != null && locale.contains(reference))
return locale.get(reference);
// Return the default
if (languages.get(Locale.forLanguageTag(Settings.defaultLanguage)).contains(reference)) {
return languages.get(Locale.forLanguageTag(Settings.defaultLanguage)).get(reference);
}
return reference;
}
/**
*
* @param sender
* @param reference
* @return the translation found for the provided reference, or the reference if nothing has been found
* Loads all the locales available. If the locale folder does not exist, one will be created and
* filled with locale files from the jar.
* TODO: Make more robust. The file filter is fragile.
*/
public String get(CommandSender sender, String reference) {
if (sender instanceof Player) {
return get(((Player)sender).getUniqueId(), reference);
}
if (reference.contains(":")) { // if reference addresses directly the module like "bskyblock:general.errors.use-in-game"
String[] path = reference.split(":", 1);
for (BSBModule module : locales.keySet()) {
if (module.getIdentifier().toLowerCase().equals(path[0].toLowerCase())) {
// CommandSender doesnt have any data stored, so we have to get the default language
BSBLocale locale = getLocale(module, Settings.defaultLanguage);
String translation = path[1];
if (locale != null) translation = locale.get(path[1]);
if (!Settings.defaultLanguage.equals("en-US") && translation.equals(path[1])) {
// If the default language is not en-US and no translation has been found (aka reference has been returned)
// then check in the en-US locale, which should always exists
locale = getLocale(module, "en-US");
if (locale != null) translation = locale.get(path[1]);
}
return translation; // translation can be found, or can be the reference
public void loadLocales(String parent) {
// Describe the filter - we only want files that are correctly named
FilenameFilter ymlFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//plugin.getLogger().info("DEBUG: filename = " + name + " parent = " + parent );
if (name.startsWith(parent) && name.toLowerCase().endsWith(".yml")) {
// See if this is a valid locale
if (name.length() < 9)
return false;
//Locale localeObject = Locale.forLanguageTag(name.substring(name.length() - 9, name.length() - 4));
return true;
}
return false;
}
} else {
// Run through each module's locales to try find the reference
for (BSBModule module : locales.keySet()) {
// CommandSender doesnt have any data stored, so we have to get the default language
BSBLocale locale = getLocale(module, Settings.defaultLanguage);
String translation = reference;
if (locale != null) translation = locale.get(reference);
if (!Settings.defaultLanguage.equals("en-US") && translation.equals(reference)) {
// If the default language is not en-US and no translation has been found (aka reference has been returned)
// then check in the en-US locale, which should always exists
locale = getLocale(module, "en-US");
if (locale != null) translation = locale.get(reference);
};
// Run through the files and store the locales
File localeDir = new File(plugin.getDataFolder(), LOCALE_FOLDER);
// If the folder does not exist, then make it and fill with the locale files from the jar
// If it does exist, then new files will NOT be written!
if (!localeDir.exists()) {
localeDir.mkdir();
FileLister lister = new FileLister(plugin);
try {
for (String name : lister.listJar(LOCALE_FOLDER)) {
plugin.saveResource(name,true);
}
if (!translation.equals(reference)) return translation; // if a translation has been found, return it. Otherwise continue.
} catch (Exception e) {
e.printStackTrace();
}
}
return reference; // Return reference to tell the user that no translation has been found
}
/**
*
* @param uuid
* @param reference
* @return the translation found for the provided reference, or the reference if nothing has been found
*/
public String get(UUID uuid, String reference) {
if (reference.contains(":")) { // if reference addresses directly the module like "bskyblock:general.errors.use-in-game"
String[] path = reference.split(":", 1);
for (BSBModule module : locales.keySet()) {
if (module.getIdentifier().toLowerCase().equals(path[0].toLowerCase())) {
// Firstly try to find the translation in player's locale
BSBLocale locale = getLocale(module, plugin.getPlayers().getLocale(uuid));
String translation = path[1];
if (locale != null) translation = locale.get(path[1]);
if (!Settings.defaultLanguage.equals(plugin.getPlayers().getLocale(uuid)) && translation.equals(path[1])) {
// If the default language is not the same than the player's one and no translation has been found (aka reference has been returned)
// then check in the default language locale, which should always exist
locale = getLocale(module, Settings.defaultLanguage);
if (locale != null) translation = locale.get(path[1]);
}
if (!plugin.getPlayers().getLocale(uuid).equals("en-US") && !Settings.defaultLanguage.equals("en-US") && translation.equals(path[1])) {
// If the player's locale is not en-US and the default language is not en-US and no translation has been found (aka reference has been returned)
// then check in the en-US locale, which should always exists
locale = getLocale(module, "en-US");
if (locale != null) translation = locale.get(path[1]);
}
return translation; // translation can be found, or can be the reference
}
}
} else {
// Run through each module's locales to try find the reference
for (BSBModule module : locales.keySet()) {
// Firstly try to find the translation in player's locale
BSBLocale locale = getLocale(module, plugin.getPlayers().getLocale(uuid));
String translation = reference;
if (locale != null) translation = locale.get(reference);
if (!Settings.defaultLanguage.equals(plugin.getPlayers().getLocale(uuid)) && translation.equals(reference)) {
// If the default language is not the same than the player's one and no translation has been found (aka reference has been returned)
// then check in the default language locale, which should always exist
locale = getLocale(module, Settings.defaultLanguage);
if (locale != null) translation = locale.get(reference);
}
if (!plugin.getPlayers().getLocale(uuid).equals("en-US") && !Settings.defaultLanguage.equals("en-US") && translation.equals(reference)) {
// If the player's locale is not en-US and the default language is not en-US and no translation has been found (aka reference has been returned)
// then check in the en-US locale, which should always exists
locale = getLocale(module, "en-US");
if (locale != null) translation = locale.get(reference);
}
if (!translation.equals(reference)) return translation; // if a translation has been found, return it. Otherwise continue.
// Store all the locales available
for (File language : localeDir.listFiles(ymlFilter)) {
if (DEBUG)
plugin.getLogger().info("DEBUG: parent = " + parent + " language = " + language.getName().substring(parent.isEmpty() ? 0 : parent.length() + 1, language.getName().length() - 4));
Locale localeObject = Locale.forLanguageTag(language.getName().substring(parent.isEmpty() ? 0 : parent.length() + 1, language.getName().length() - 4));
if (DEBUG)
plugin.getLogger().info("DEBUG: locale country found = " + localeObject.getCountry());
if (languages.containsKey(localeObject)) {
// Merge into current language
languages.get(localeObject).add(language);
} else {
// New language
languages.put(localeObject, new BSBLocale(localeObject, language));
}
}
return reference; // Return reference to tell the user that no translation has been found
}
public Map<BSBModule, List<BSBLocale>> getLocales() {
return locales;
}
public List<BSBLocale> getLocales(BSBModule module) {
return locales.get(module);
}
public BSBLocale getLocale(BSBModule module, String languageTag) {
for (BSBLocale locale : locales.get(module)) {
if (locale.toLanguageTag().equals(languageTag)) return locale;
}
return null;
}
public void registerLocales(BSBModule module) {
// Check if the folder exists and contains the locales
// for each languageTag found, do registerLocale(module, languageTag)
//TODO
}
public void registerLocale(BSBModule module, String languageTag) {
//TODO
}
public void registerExternalLocale(BSBModule originModule, BSBModule targetModule, String languageTag) {
//TODO
}
}