package dev.loki.loparkour.config.locale; import dev.loki.loparkour.LoParkour; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; /** * Loads locale files from disk with validation and auto-fix. */ public final class LocaleLoader { private LocaleLoader() { } /** * Load all locale files from the locales/ folder. * Returns a map of locale name → FileConfiguration. */ @NotNull public static Map loadAll() { final Plugin plugin = LoParkour.getPlugin(); try { final FileConfiguration embedded = YamlConfiguration.loadConfiguration( new InputStreamReader(plugin.getResource("locales/en.yml"), StandardCharsets.UTF_8)); return loadFromDisk(embedded); } catch (Exception ex) { // Boundary: locale loading must never break enable; disk files may be corrupt. plugin.getLogger().severe("Error while loading locale files: " + ex.getMessage()); return Collections.emptyMap(); } } @NotNull private static Map loadFromDisk(@NotNull FileConfiguration embedded) throws Exception { final Map result = new HashMap<>(); final File folder = LoParkour.getInFolder("locales"); ensureFolder(folder); try (Stream stream = Files.list(folder.toPath())) { stream.forEach(path -> { final File file = path.toFile(); if (!file.getName().endsWith(".yml")) { return; } final String locale = file.getName().split("\\.")[0]; LoParkour.log("Found locale " + locale); final FileConfiguration config = YamlConfiguration.loadConfiguration(file); validateAndFix(embedded, config, file); result.put(locale, config); }); } return result; } private static void ensureFolder(@NotNull File folder) { if (!folder.exists()) { folder.mkdirs(); } final String[] files = folder.list(); if (files == null || files.length == 0) { final Plugin plugin = LoParkour.getPlugin(); plugin.saveResource("locales/en.yml", false); plugin.saveResource("locales/ru.yml", false); } } /** * Validate a locale file against the reference (en.yml) and auto-fix missing keys. */ private static void validateAndFix(@NotNull FileConfiguration reference, @NotNull FileConfiguration locale, @NotNull File file) { final List refNodes = getChildren(reference); final List locNodes = getChildren(locale); boolean modified = false; for (final String node : refNodes) { if (!locNodes.contains(node)) { LoParkour.log("Fixing missing config node %s in %s".formatted(node, file.getName())); locale.set(node, reference.get(node)); modified = true; } } if (modified) { try { locale.save(file); } catch (IOException ex) { LoParkour.getPlugin().getLogger().severe( "Error saving fixed locale %s — delete and restart — %s".formatted(file.getName(), ex.getMessage())); } } LoParkour.log("Validated locale " + file.getName()); } @NotNull private static List getChildren(@NotNull FileConfiguration config) { final ConfigurationSection section = config.getConfigurationSection(""); return section != null ? new ArrayList<>(section.getKeys(true)) : Collections.emptyList(); } }