diff --git a/crowdin.yml b/crowdin.yml index 667b629d2..4aa643a06 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,4 +1,3 @@ files: - - source: /src/main/resources/languages/en/*.yml - translation: /src/main/resources/languages/%osx_locale%/%original_file_name% - + - source: /src/main/resources/translations/en/*.yml + translation: /src/main/resources/translations/%osx_locale%/%original_file_name% diff --git a/pom.xml b/pom.xml index a1a54ebd0..d106060cb 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ true * + translations/**/*.yml diff --git a/src/main/java/net/guizhanss/slimefuntranslation/SlimefunTranslation.java b/src/main/java/net/guizhanss/slimefuntranslation/SlimefunTranslation.java index a76fdf93b..6c6218c4b 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/SlimefunTranslation.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/SlimefunTranslation.java @@ -4,6 +4,10 @@ import java.lang.reflect.Method; import java.util.logging.Level; +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; + import org.bukkit.plugin.Plugin; import io.github.thebusybiscuit.slimefun4.libraries.dough.updater.BlobBuildUpdater; @@ -11,6 +15,7 @@ import net.guizhanss.guizhanlib.slimefun.addon.AbstractAddon; import net.guizhanss.guizhanlib.updater.GuizhanBuildsUpdater; import net.guizhanss.slimefuntranslation.core.Registry; +import net.guizhanss.slimefuntranslation.core.services.ConfigurationService; import net.guizhanss.slimefuntranslation.implementation.managers.CommandManager; import net.guizhanss.slimefuntranslation.implementation.managers.ListenerManager; import net.guizhanss.slimefuntranslation.implementation.managers.PacketListenerManager; @@ -21,9 +26,11 @@ public final class SlimefunTranslation extends AbstractAddon { + private ConfigurationService configService; private Registry registry; private UserManager userManager; private TranslationManager translationManager; + private boolean debugEnabled = false; public SlimefunTranslation() { super("ybw0014", "SlimefunTranslation", "master", "auto-update"); @@ -33,18 +40,34 @@ private static SlimefunTranslation inst() { return getInstance(); } + @Nonnull + public static ConfigurationService getConfigService() { + return inst().configService; + } + + @Nonnull public static Registry getRegistry() { return inst().registry; } + @Nonnull public static UserManager getUserManager() { return inst().userManager; } + @Nonnull public static TranslationManager getTranslationManager() { return inst().translationManager; } + public static void debug(@Nonnull String message, @Nonnull Object... args) { + Preconditions.checkNotNull(message, "message cannot be null"); + + if (inst().debugEnabled) { + inst().getLogger().log(Level.INFO, "[DEBUG] " + message, args); + } + } + @Override public void enable() { log(Level.INFO, "===================="); @@ -52,18 +75,27 @@ public void enable() { log(Level.INFO, " by ybw0014 "); log(Level.INFO, "===================="); + // config + configService = new ConfigurationService(this); + + // registry registry = new Registry(); + + // debug + debugEnabled = configService.isDebug(); + + // managers userManager = new UserManager(); - translationManager = new TranslationManager(this); + translationManager = new TranslationManager(this, getFile()); new CommandManager(this); new ListenerManager(this); new PacketListenerManager(); + // metrics setupMetrics(); - getScheduler().runAsync(() -> { - translationManager.loadTranslations(); - }); + // delayed tasks + getScheduler().runAsync(() -> translationManager.loadTranslations()); } @Override diff --git a/src/main/java/net/guizhanss/slimefuntranslation/api/TranslationConfiguration.java b/src/main/java/net/guizhanss/slimefuntranslation/api/TranslationConfiguration.java index 7da76ccee..2add36877 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/api/TranslationConfiguration.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/api/TranslationConfiguration.java @@ -1,25 +1,133 @@ package net.guizhanss.slimefuntranslation.api; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; + +import net.guizhanss.slimefuntranslation.implementation.translations.FixedTranslation; +import net.guizhanss.slimefuntranslation.utils.ConfigUtils; + +import org.bukkit.configuration.file.FileConfiguration; + +import io.github.thebusybiscuit.slimefun4.api.SlimefunAddon; + +import net.guizhanss.slimefuntranslation.SlimefunTranslation; import net.guizhanss.slimefuntranslation.api.interfaces.Translation; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.Setter; /** * This class holds the information provided from a valid translations file, or from other addons. * * @author ybw0014 */ +@SuppressWarnings("ConstantConditions") @RequiredArgsConstructor @Getter public class TranslationConfiguration { private final String name; private final String author; - private final boolean enabled; private final String lang; + private final List dependencies; + private final Map translations; + + @Setter(AccessLevel.PRIVATE) + private State state = State.UNREGISTERED; + private SlimefunAddon addon = null; + + /** + * Creates a {@link TranslationConfiguration} from a {@link FileConfiguration}. + * + * @param config + * the {@link FileConfiguration} to create the {@link TranslationConfiguration} from. + * + * @return an {@link Optional} of {@link TranslationConfiguration} if the config is valid, otherwise {@code null}. + */ + @Nonnull + public static Optional fromFileConfiguration(@Nonnull FileConfiguration config) { + Preconditions.checkArgument(config != null, "config cannot be null"); + + String name = config.getString("name", "Unnamed Translation"); + String author = config.getString("author", "SlimefunTranslation"); + String lang = SlimefunTranslation.getConfigService().getMappedLanguage(config.getString("lang", "en")); + List dependencies = config.getStringList("dependencies"); + + for (var dependency : dependencies) { + if (!SlimefunTranslation.getInstance().getServer().getPluginManager().isPluginEnabled(dependency)) { + SlimefunTranslation.log(Level.SEVERE, "Translation config \"{0}\" by {1} is missing dependency {2}.", name, author, dependency); + return Optional.empty(); + } + } + + var section = config.getConfigurationSection("translations"); + if (section == null) { + SlimefunTranslation.log(Level.WARNING, "No translations found in " + name + " by " + author); + return Optional.empty(); + } + SlimefunTranslation.log(Level.INFO, "Loading translation configuration \"{0}\" by {1}, language: {2}", name, author, lang); + Map translations = new HashMap<>(); + for (var itemId : section.getKeys(false)) { + SlimefunTranslation.debug("Loading translation {0}", itemId); + var itemSection = section.getConfigurationSection(itemId); + // name + String displayName = ""; + if (itemSection.contains("name")) { + displayName = itemSection.getString("name", ""); + } + + // lore + var lore = itemSection.getStringList("lore"); + + // lore replacements + Map replacementMap = new HashMap<>(); + if (itemSection.contains("lore-replacements")) { + try { + Map replacements = ConfigUtils.getMap(itemSection.getConfigurationSection("lore-replacements")); + for (var entry : replacements.entrySet()) { + replacementMap.put(Integer.parseInt(entry.getKey()), entry.getValue()); + } + } catch (NumberFormatException | NullPointerException ex) { + SlimefunTranslation.log(Level.SEVERE, "Invalid lore replacements of item {0} in translation {1} by {2}", itemId, name, author); + return Optional.empty(); + } + } + + // check name + boolean checkName = itemSection.getBoolean("check-name", false); + + var translation = new FixedTranslation(displayName, lore, replacementMap, checkName); + translations.put(itemId, translation); + } + return Optional.of(new TranslationConfiguration(name, author, lang, dependencies, translations)); + } + + public void register(@Nonnull SlimefunAddon addon) { + if (state != State.UNREGISTERED) { + throw new IllegalStateException("TranslationConfiguration is already registered"); + } + + var allTranslations = SlimefunTranslation.getRegistry().getTranslations(); + allTranslations.putIfAbsent(lang, new HashMap<>()); + var currentTranslations = allTranslations.get(lang); + currentTranslations.putAll(translations); + + this.addon = addon; + setState(State.REGISTERED); + } - private final Map translations = new HashMap<>(); + public enum State { + UNREGISTERED, + REGISTERED + } } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/api/interfaces/Translation.java b/src/main/java/net/guizhanss/slimefuntranslation/api/interfaces/Translation.java index c3513ce25..483798482 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/api/interfaces/Translation.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/api/interfaces/Translation.java @@ -3,8 +3,13 @@ import java.util.List; import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; -import net.guizhanss.slimefuntranslation.api.translations.FixedTranslation; +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; + +import net.guizhanss.slimefuntranslation.implementation.translations.FixedTranslation; + +import org.bukkit.inventory.ItemStack; /** * This interface represents a translation. @@ -13,8 +18,18 @@ */ public interface Translation { @Nonnull - String getDisplayName(String original); + String getDisplayName(@Nonnull String original); @Nonnull - List getLore(List original); + List getLore(@Nonnull List original); + + /** + * Override this method if you need extra check to make sure item can be translated. + * @param item The {@link ItemStack} to check. + * @return Whether the item can be translated. + */ + @ParametersAreNonnullByDefault + default boolean canTranslate(ItemStack item, SlimefunItem sfItem) { + return true; + } } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/api/translations/FixedTranslation.java b/src/main/java/net/guizhanss/slimefuntranslation/api/translations/FixedTranslation.java deleted file mode 100644 index 05535699e..000000000 --- a/src/main/java/net/guizhanss/slimefuntranslation/api/translations/FixedTranslation.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.guizhanss.slimefuntranslation.api.translations; - -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; - -import net.guizhanss.slimefuntranslation.api.interfaces.Translation; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class FixedTranslation implements Translation { - private final String displayName; - private final List lore; - private final Map replacements; - - @Override - @Nonnull - public String getDisplayName(@Nonnull String original) { - return displayName; - } -} diff --git a/src/main/java/net/guizhanss/slimefuntranslation/core/Registry.java b/src/main/java/net/guizhanss/slimefuntranslation/core/Registry.java index 3b9ed6577..292aa41de 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/core/Registry.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/core/Registry.java @@ -12,5 +12,5 @@ @Getter public final class Registry { private final Map users = new HashMap<>(); - private final Map translations = new HashMap<>(); + private final Map> translations = new HashMap<>(); } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/core/services/ConfigurationService.java b/src/main/java/net/guizhanss/slimefuntranslation/core/services/ConfigurationService.java new file mode 100644 index 000000000..9d1964954 --- /dev/null +++ b/src/main/java/net/guizhanss/slimefuntranslation/core/services/ConfigurationService.java @@ -0,0 +1,45 @@ +package net.guizhanss.slimefuntranslation.core.services; + +import java.util.Map; + +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; + +import net.guizhanss.guizhanlib.slimefun.addon.AddonConfig; +import net.guizhanss.slimefuntranslation.SlimefunTranslation; +import net.guizhanss.slimefuntranslation.utils.ConfigUtils; + +import lombok.AccessLevel; +import lombok.Getter; + +@SuppressWarnings("ConstantConditions") +@Getter +public final class ConfigurationService { + @Getter(AccessLevel.NONE) + private final AddonConfig config; + private Map languageMappings; + private boolean autoUpdate; + private boolean debug; + + public ConfigurationService(SlimefunTranslation plugin) { + config = new AddonConfig(plugin, "config.yml"); + reload(); + } + + public void reload() { + config.reload(); + + autoUpdate = config.getBoolean("auto-update", true); + debug = config.getBoolean("debug", false); + languageMappings = ConfigUtils.getMap(config.getConfigurationSection("language-mappings")); + + config.save(); + } + + @Nonnull + public String getMappedLanguage(@Nonnull String language) { + Preconditions.checkArgument(language != null, "language cannot be null"); + return SlimefunTranslation.getConfigService().getLanguageMappings().getOrDefault(language, language); + } +} diff --git a/src/main/java/net/guizhanss/slimefuntranslation/core/users/User.java b/src/main/java/net/guizhanss/slimefuntranslation/core/users/User.java index 8a5869418..324b905c8 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/core/users/User.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/core/users/User.java @@ -2,6 +2,10 @@ import java.util.UUID; +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; + import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -20,19 +24,23 @@ public class User { private final UUID uuid; private String locale; - public User(Player player) { + public User(@Nonnull Player player) { this.player = player; this.uuid = player.getUniqueId(); init(); } - public User(UUID uuid) { + public User(@Nonnull UUID uuid) { this.player = Bukkit.getPlayer(uuid); this.uuid = uuid; init(); } private void init() { + updateLocale(); + } + + public void updateLocale() { var lang = Slimefun.getLocalization().getLanguage(player); if (lang != null) { locale = lang.getId(); @@ -40,4 +48,9 @@ private void init() { locale = Slimefun.getLocalization().getDefaultLanguage().getId(); } } + + public void updateLocale(@Nonnull String newLocale) { + Preconditions.checkArgument(newLocale != null, "Locale cannot be null"); + locale = newLocale; + } } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/implementation/listeners/SlimefunLanguageChangeListener.java b/src/main/java/net/guizhanss/slimefuntranslation/implementation/listeners/SlimefunLanguageChangeListener.java new file mode 100644 index 000000000..7d73993b1 --- /dev/null +++ b/src/main/java/net/guizhanss/slimefuntranslation/implementation/listeners/SlimefunLanguageChangeListener.java @@ -0,0 +1,22 @@ +package net.guizhanss.slimefuntranslation.implementation.listeners; + +import io.github.thebusybiscuit.slimefun4.api.events.PlayerLanguageChangeEvent; + +import net.guizhanss.slimefuntranslation.SlimefunTranslation; + +import net.guizhanss.slimefuntranslation.core.users.User; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class SlimefunLanguageChangeListener implements Listener { + public SlimefunLanguageChangeListener(SlimefunTranslation plugin) { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onJoin(PlayerLanguageChangeEvent e) { + User user = SlimefunTranslation.getUserManager().getUser(e.getPlayer()); + user.updateLocale(e.getNewLanguage().getId()); + } +} diff --git a/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/ListenerManager.java b/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/ListenerManager.java index 8debc8e4d..e8c47ac85 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/ListenerManager.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/ListenerManager.java @@ -5,10 +5,12 @@ import net.guizhanss.slimefuntranslation.SlimefunTranslation; import net.guizhanss.slimefuntranslation.implementation.listeners.PlayerJoinListener; import net.guizhanss.slimefuntranslation.implementation.listeners.PlayerQuitListener; +import net.guizhanss.slimefuntranslation.implementation.listeners.SlimefunLanguageChangeListener; public final class ListenerManager { public ListenerManager(@Nonnull SlimefunTranslation plugin) { new PlayerJoinListener(plugin); new PlayerQuitListener(plugin); + new SlimefunLanguageChangeListener(plugin); } } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/TranslationManager.java b/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/TranslationManager.java index 3dbd47f6c..77ff65f75 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/TranslationManager.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/implementation/managers/TranslationManager.java @@ -1,6 +1,8 @@ package net.guizhanss.slimefuntranslation.implementation.managers; import java.io.File; +import java.util.ArrayList; +import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -8,46 +10,69 @@ import com.google.common.base.Preconditions; -import net.guizhanss.slimefuntranslation.api.TranslationConfiguration; +import net.guizhanss.guizhanlib.minecraft.utils.ChatUtil; +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import net.guizhanss.guizhanlib.slimefun.addon.AddonConfig; import net.guizhanss.slimefuntranslation.SlimefunTranslation; +import net.guizhanss.slimefuntranslation.api.TranslationConfiguration; import net.guizhanss.slimefuntranslation.api.interfaces.Translatable; +import net.guizhanss.slimefuntranslation.api.interfaces.Translation; import net.guizhanss.slimefuntranslation.core.users.User; +import net.guizhanss.slimefuntranslation.utils.FileUtils; public final class TranslationManager { private static final String FOLDER_NAME = "translations"; + private static final String DEFAULT_LANGUAGE = "en"; + private final File translationsFolder; - public TranslationManager(SlimefunTranslation plugin) { - File translationsFolder = new File(plugin.getDataFolder(), FOLDER_NAME); + @ParametersAreNonnullByDefault + public TranslationManager(SlimefunTranslation plugin, File jarFile) { + translationsFolder = new File(plugin.getDataFolder(), FOLDER_NAME); if (!translationsFolder.exists()) { translationsFolder.mkdirs(); + + // also unzip the example translations + List translationFiles = FileUtils.listYamlFilesInJar(jarFile, FOLDER_NAME + File.separator); + for (String translationFile : translationFiles) { + plugin.saveResource(FOLDER_NAME + File.separator + translationFile, false); + } } } public void loadTranslations() { - + loadFixedTranslations(); + loadProgrammedTranslations(); } private void loadFixedTranslations() { - // TODO: Load file translations - } + List translationFiles = FileUtils.listYamlFiles(translationsFolder); + for (String translationFile : translationFiles) { + var config = YamlConfiguration.loadConfiguration(new File(translationsFolder, translationFile)); + var translationConfig = TranslationConfiguration.fromFileConfiguration(config); + if (translationConfig.isEmpty()) { + continue; + } + translationConfig.get().register(SlimefunTranslation.getInstance()); + } - private void loadProgrammedTranslations() { - // TODO: Load Translatable SlimefunItem translations } - /** - * Register a {@link TranslationConfiguration}. - * This can be called from the loading methods in this class, or by other plugins if they don't want to implement {@link Translatable}. - * @param configuration The {@link TranslationConfiguration}. - */ - public void registerTranslationConfiguration(@Nonnull TranslationConfiguration configuration) { + private void loadProgrammedTranslations() { + for (SlimefunItem sfItem : Slimefun.getRegistry().getAllSlimefunItems()) { + if (!(sfItem instanceof Translatable translatable)) { + continue; + } + // TODO: complete this + } } /** @@ -76,17 +101,64 @@ public boolean translateItem(@Nonnull User user, @Nullable ItemStack item) { @ParametersAreNonnullByDefault private boolean translateItem(User user, ItemStack item, SlimefunItem sfItem) { - if (sfItem instanceof Translatable translatable) { - ItemMeta meta = item.getItemMeta(); - if (meta.hasDisplayName()) { - meta.setDisplayName(translatable.getTranslatedDisplayName(meta.getDisplayName())); + final Translation translation = findItemTranslation(user, item, sfItem.getId()); + if (translation == null) { + return false; + } + + if (!translation.canTranslate(item, sfItem)) { + return false; + } + + final ItemMeta meta = item.getItemMeta(); + String originalDisplayName = meta.hasDisplayName() ? meta.getDisplayName() : ""; + meta.setDisplayName(ChatUtil.color(translation.getDisplayName(originalDisplayName))); + List originalLore = meta.hasLore() ? meta.getLore() : new ArrayList<>(); + meta.setLore(ChatUtil.color(translation.getLore(originalLore))); + item.setItemMeta(meta); + return true; + } + + @Nullable + @ParametersAreNonnullByDefault + private Translation findItemTranslation(User user, ItemStack item, String id) { + SlimefunTranslation.debug("Attempting to find the translation for item {0} for user {1}", id, user.getPlayer().getName()); + var allTranslations = SlimefunTranslation.getRegistry().getTranslations(); + // find the translations for user's current locale + var translations = allTranslations.get(user.getLocale()); + if (translations != null) { + // then find the translation for the item + var translation = translations.get(id); + if (translation != null) { + return translation; } - if (meta.hasLore()) { - meta.setLore(translatable.getTranslatedLore(meta.getLore())); + } + + SlimefunTranslation.debug("User's locale {0} does not have translation for item.", user.getLocale()); + + // user's current locale does not have translation for the given item, + // try server default locale + var serverDefault = Slimefun.getLocalization().getDefaultLanguage(); + if (serverDefault != null) { + translations = allTranslations.get(serverDefault.getId()); + if (translations != null) { + var translation = translations.get(id); + if (translation != null) { + return translation; + } } - return true; } - return false; + SlimefunTranslation.debug("Server default locale {0} does not have translation for item.", serverDefault); + + // try english at last + translations = allTranslations.get(DEFAULT_LANGUAGE); + if (translations != null) { + return translations.get(id); + } + + SlimefunTranslation.debug("English does not have translation for item."); + + return null; } } diff --git a/src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/FixedTranslation.java b/src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/FixedTranslation.java new file mode 100644 index 000000000..c66f8ddca --- /dev/null +++ b/src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/FixedTranslation.java @@ -0,0 +1,81 @@ +package net.guizhanss.slimefuntranslation.implementation.translations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.ParametersAreNonnullByDefault; + +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; + +import net.guizhanss.slimefuntranslation.api.interfaces.Translation; + +import lombok.RequiredArgsConstructor; + +import org.bukkit.inventory.ItemStack; + +/** + * A fixed translation is defined from config file, or from other plugins. + */ +@RequiredArgsConstructor +public class FixedTranslation implements Translation { + private final String displayName; + private final List lore; + private final Map replacements; + private final boolean checkName; + + /** + * Get the display name of the item. + * If the defined translated display name is empty, the original display name will be returned. + * + * @param original + * The original display name. + * + * @return The translated display name. + */ + @Override + @Nonnull + public String getDisplayName(@Nonnull String original) { + return displayName.isEmpty() ? original : displayName; + } + + /** + * Get the lore of the item. + * If the defined translated lore is empty, it will start replacing the lore lines. + * + * @param original + * The original lore. + * + * @return The translated lore. + */ + @Override + @Nonnull + public List getLore(@Nonnull List original) { + if (lore.isEmpty()) { + var newLore = new ArrayList<>(original); + for (var entry : replacements.entrySet()) { + try { + newLore.set(entry.getKey() - 1, entry.getValue()); + } catch (IndexOutOfBoundsException e) { + // ignore + } + } + return newLore; + } else { + return new ArrayList<>(lore); + } + } + + @Override + @ParametersAreNonnullByDefault + public boolean canTranslate(ItemStack item, SlimefunItem sfItem) { + if (!checkName) { + return true; + } + + var originalDisplayName = sfItem.getItemName(); + var meta = item.getItemMeta(); + return meta.hasDisplayName() && meta.getDisplayName().equals(originalDisplayName); + } +} diff --git a/src/main/java/net/guizhanss/slimefuntranslation/api/translations/ProgrammedTranslation.java b/src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/ProgrammedTranslation.java similarity index 92% rename from src/main/java/net/guizhanss/slimefuntranslation/api/translations/ProgrammedTranslation.java rename to src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/ProgrammedTranslation.java index 10e71cbc1..27080c2f4 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/api/translations/ProgrammedTranslation.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/implementation/translations/ProgrammedTranslation.java @@ -1,14 +1,15 @@ -package net.guizhanss.slimefuntranslation.api.translations; +package net.guizhanss.slimefuntranslation.implementation.translations; -import lombok.RequiredArgsConstructor; +import java.util.List; + +import javax.annotation.Nonnull; -import net.guizhanss.slimefuntranslation.api.interfaces.Translation; -import net.guizhanss.slimefuntranslation.api.interfaces.Translatable; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; -import javax.annotation.Nonnull; +import net.guizhanss.slimefuntranslation.api.interfaces.Translatable; +import net.guizhanss.slimefuntranslation.api.interfaces.Translation; -import java.util.List; +import lombok.RequiredArgsConstructor; /** * This {@link Translation} is applied by {@link SlimefunItem}s which implemented {@link Translatable}. diff --git a/src/main/java/net/guizhanss/slimefuntranslation/utils/ConfigUtils.java b/src/main/java/net/guizhanss/slimefuntranslation/utils/ConfigUtils.java new file mode 100644 index 000000000..a5cb65de0 --- /dev/null +++ b/src/main/java/net/guizhanss/slimefuntranslation/utils/ConfigUtils.java @@ -0,0 +1,29 @@ +package net.guizhanss.slimefuntranslation.utils; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.bukkit.configuration.ConfigurationSection; + +import lombok.experimental.UtilityClass; + +@SuppressWarnings("unchecked") +@UtilityClass +public final class ConfigUtils { + + public static Map getMap(@Nullable ConfigurationSection section) { + Map map = new HashMap<>(); + if (section == null) { + return map; + } + for (String key : section.getKeys(false)) { + var value = section.get(key); + if (value instanceof String || value instanceof Integer || value instanceof Double || value instanceof Boolean) { + map.put(key, (V) value); + } + } + return map; + } +} diff --git a/src/main/java/net/guizhanss/slimefuntranslation/utils/FileUtils.java b/src/main/java/net/guizhanss/slimefuntranslation/utils/FileUtils.java index 8e80e36eb..e0ad0f4e9 100644 --- a/src/main/java/net/guizhanss/slimefuntranslation/utils/FileUtils.java +++ b/src/main/java/net/guizhanss/slimefuntranslation/utils/FileUtils.java @@ -1,13 +1,20 @@ package net.guizhanss.slimefuntranslation.utils; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Enumeration; import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.logging.Level; import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import net.guizhanss.slimefuntranslation.SlimefunTranslation; + import lombok.experimental.UtilityClass; @SuppressWarnings("ConstantConditions") @@ -40,12 +47,12 @@ private static List listYamlFiles(File folder, String path) { List result = new ArrayList<>(); for (File file : files) { - String filename = file.getName(); + final String filename = file.getName(); if (filename.startsWith(".") || filename.startsWith("_")) { continue; } if (file.isDirectory()) { - String subFolderPath = path + filename + "/"; + String subFolderPath = path + filename + File.separator; result.addAll(listYamlFiles(file, subFolderPath)); } else { if (filename.endsWith(".yml") || filename.endsWith(".yaml")) { @@ -55,4 +62,27 @@ private static List listYamlFiles(File folder, String path) { } return result; } + + @ParametersAreNonnullByDefault + public List listYamlFilesInJar(File jarFile, String folderPath) { + if (jarFile == null || !jarFile.isFile()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + try (JarFile jar = new JarFile(jarFile)) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + String filename = entryName.substring(entryName.lastIndexOf("/") + 1); + // Check if it's a .yml file and is under the "translations" folder + if (entryName.startsWith(folderPath) && filename.endsWith(".yml") && !entry.isDirectory()) { + result.add(entryName.replace(folderPath, "")); + } + } + } catch (IOException ex) { + SlimefunTranslation.log(Level.SEVERE, ex, "An error has occurred while listing YAML files in jar file {0}", jarFile.getName()); + } + return result; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4a65eabd6..b90a11de0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,6 +1,9 @@ # Whether to enable auto update auto-update: true +# Print debug messages in console. +debug: false + # Language mappings # Format: : language-mappings: diff --git a/src/main/resources/languages/en/slimefun-weapons.yml b/src/main/resources/languages/en/slimefun-weapons.yml deleted file mode 100644 index f4df69c0c..000000000 --- a/src/main/resources/languages/en/slimefun-weapons.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: Slimefun Weapons Translation -author: Slimefun -enabled: true -lang: en -translations: - diff --git a/src/main/resources/translations/slimefun/en/weapons.yml b/src/main/resources/translations/slimefun/en/weapons.yml new file mode 100644 index 000000000..b4e8d7650 --- /dev/null +++ b/src/main/resources/translations/slimefun/en/weapons.yml @@ -0,0 +1,48 @@ +# These files are used by Crowdin to translate them into other languages, and will be provided by default. +name: Slimefun Weapons +author: Slimefun +lang: en +dependencies: + - Slimefun +translations: + GRANDMAS_WALKING_STICK: + name: "&7Grandmas Walking Stick" + GRANDPAS_WALKING_STICK: + name: "&7Grandpas Walking Stick" + SWORD_OF_BEHEADING: + name: "&6Sword of Beheading" + lore: + - "&7Beheading II" + - "" + - "&fHas a chance to behead Mobs" + - "&f(even a higher chance for Wither Skeletons)" + BLADE_OF_VAMPIRES: + check-name: true + name: "&4Blade of Vampires" + lore: + - "&7Life Steal I" + - "" + - "&fEverytime you attack something" + - "&fyou have a 45% chance to" + - "&frecover 2 Hearts of your Health" + SEISMIC_AXE: + name: "&aSeismic Axe" + lore-replacements: + 2: "&7&oA portable Earthquake..." + SOULDBOUND_SWORD: + name: "&cSoulbound Sword" + SOULBOUND_TRIDENT: + name: "&cSoulbound Trident" + SOULBOUND_BOW: + name: "&cSoulbound Bow" + EXPLOSIVE_BOW: + name: "&cExplosive Bow" + lore: + - "&fAny Arrows fired using this Bow" + - "&fwill launch hit enemies into the air" + ICY_BOW: + name: "&bIcy Bow" + lore: + - "&fAny Arrows fired using this Bow" + - "&fwill prevent hit enemies from moving" + - "&ffor 2 seconds"