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"