diff --git a/build.gradle b/build.gradle index c00c81d..546d648 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,10 @@ repositories { name = 'Terraformers' url = 'https://maven.terraformersmc.com/releases/' } + maven { + name = 'NotEnoughUpdates' + url = 'https://maven.notenoughupdates.org/releases/' + } } loom { @@ -50,6 +54,12 @@ dependencies { implementation "dev.isxander:yet-another-config-lib:${project.yacl_version}" include "dev.isxander:yet-another-config-lib:${project.yacl_version}" + // MoulConfig (NEU/SkyHanni-style config UI); written in Kotlin, so FLK comes along + implementation "org.notenoughupdates.moulconfig:modern-26.2:${project.moulconfig_version}" + include "org.notenoughupdates.moulconfig:modern-26.2:${project.moulconfig_version}" + implementation "net.fabricmc:fabric-language-kotlin:${project.flk_version}" + include "net.fabricmc:fabric-language-kotlin:${project.flk_version}" + // Mod Menu integration (optional at runtime; present in dev for testing) compileOnly "com.terraformersmc:modmenu:${project.modmenu_version}" runtimeOnly "com.terraformersmc:modmenu:${project.modmenu_version}" diff --git a/gradle.properties b/gradle.properties index 4e8392a..603e7f9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,10 +12,12 @@ loader_version=0.19.3 loom_version=1.17-SNAPSHOT # Mod Properties -mod_version=0.2.0-alpha +mod_version=0.3.0-alpha maven_group=dev.laybalt # Dependencies fabric_api_version=0.155.2+26.2 yacl_version=3.9.6+26.2-fabric modmenu_version=20.0.1 +moulconfig_version=4.7.2 +flk_version=1.13.13+kotlin.2.4.10 diff --git a/src/main/java/dev/laybalt/skyblocktranslator/SkyblockTranslatorClient.java b/src/main/java/dev/laybalt/skyblocktranslator/SkyblockTranslatorClient.java index 36d13e9..0a775c1 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/SkyblockTranslatorClient.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/SkyblockTranslatorClient.java @@ -11,9 +11,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.laybalt.skyblocktranslator.config.ConfigHolder; import dev.laybalt.skyblocktranslator.config.ModConfig; import dev.laybalt.skyblocktranslator.pipeline.TranslationEngine; -import dev.laybalt.skyblocktranslator.ui.ConfigScreens; public class SkyblockTranslatorClient implements ClientModInitializer { public static final String MOD_ID = "skyblock-translator"; @@ -31,11 +31,12 @@ public void onInitializeClient() { KeyMapping.Category.register(Identifier.fromNamespaceAndPath(MOD_ID, "main")))); ClientTickEvents.END_CLIENT_TICK.register(client -> { while (openConfig.consumeClick()) { - client.setScreenAndShow(ConfigScreens.create(null)); + ConfigHolder.openGui(); } }); ClientLifecycleEvents.CLIENT_STOPPING.register(client -> TranslationEngine.get().shutdown()); - LOGGER.info("SkyBlock Translator initialized (language={}, enabled={})", config.language, config.enabled); + LOGGER.info("SkyBlock Translator initialized (language={}, enabled={})", + config.languageCode(), config.general.enabled); } } diff --git a/src/main/java/dev/laybalt/skyblocktranslator/config/ConfigHolder.java b/src/main/java/dev/laybalt/skyblocktranslator/config/ConfigHolder.java new file mode 100644 index 0000000..0af8c8f --- /dev/null +++ b/src/main/java/dev/laybalt/skyblocktranslator/config/ConfigHolder.java @@ -0,0 +1,44 @@ +package dev.laybalt.skyblocktranslator.config; + +import java.io.File; + +import io.github.notenoughupdates.moulconfig.managed.ManagedConfig; +import io.github.notenoughupdates.moulconfig.managed.ManagedConfigBuilder; + +import dev.laybalt.skyblocktranslator.pipeline.TranslationEngine; + +/** + * Owns the {@link ManagedConfig} wrapper around {@link ModConfig}: MoulConfig + * handles persistence and the editor screen; after every save the translation + * engine is rebuilt so language/provider changes apply immediately. + */ +public final class ConfigHolder { + private static ManagedConfig managed; + + private ConfigHolder() { + } + + public static synchronized ManagedConfig managed() { + if (managed == null) { + File file = ModConfig.directory().resolve("config.json").toFile(); + file.getParentFile().mkdirs(); + ManagedConfigBuilder builder = new ManagedConfigBuilder<>(file, ModConfig.class); + builder.setAfterSave(unused -> TranslationEngine.reload()); + managed = new ManagedConfig<>(builder); + } + return managed; + } + + public static ModConfig get() { + return managed().getInstance(); + } + + public static void save() { + managed().saveToFile(); + } + + /** Opens the MoulConfig settings screen. */ + public static void openGui() { + managed().openConfigGui(); + } +} diff --git a/src/main/java/dev/laybalt/skyblocktranslator/config/ModConfig.java b/src/main/java/dev/laybalt/skyblocktranslator/config/ModConfig.java index fdde91f..89e7721 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/config/ModConfig.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/config/ModConfig.java @@ -1,94 +1,170 @@ package dev.laybalt.skyblocktranslator.config; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; +import io.github.notenoughupdates.moulconfig.Config; +import io.github.notenoughupdates.moulconfig.annotations.Category; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorButton; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider; +import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorText; +import io.github.notenoughupdates.moulconfig.annotations.ConfigOption; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.Minecraft; -import dev.laybalt.skyblocktranslator.SkyblockTranslatorClient; +import dev.laybalt.skyblocktranslator.ui.ConfigScreens; /** - * Mod configuration, stored as {@code config/skyblock-translator/config.json}. - * A missing file is created with defaults on first launch. + * Mod configuration, rendered with MoulConfig (the NEU/SkyHanni config UI) and + * persisted by {@link ConfigHolder} as {@code config/skyblock-translator/config.json}. + * + *

Dropdowns are index-based; use {@link #languageCode()} and + * {@link #onlineProviderCode()} instead of reading the raw indices. */ -public final class ModConfig { - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static ModConfig instance; - - public boolean enabled = true; - /** Dictionary/cache language, matches the dict folder name. */ - public String language = "ru_ru"; - /** Only translate while connected to Hypixel. Turn off to test in singleplayer. */ - public boolean onlyOnHypixel = true; - public boolean translateItems = true; - public boolean translateMenus = true; - /** NPC dialogues and other server system messages in chat. */ - public boolean translateDialogs = true; - /** Messages written by other players; off by default. */ - public boolean translatePlayerChat = false; - public boolean translateScoreboard = true; - /** Tab entries never use online MT (player names must not be machine-translated). */ - public boolean translateTabList = true; - public boolean translateBossBar = true; - /** Send unknown strings to an online translator and cache the results. */ - public boolean translateOnline = true; - /** "google" (free, zero setup) or "libretranslate" (own instance below). */ - public String onlineProvider = "google"; - public String libreTranslateUrl = ""; - public String libreTranslateApiKey = ""; - /** Max online translation requests per day. */ - public int dailyOnlineBudget = 2000; - /** Append unknown templates to config/skyblock-translator/untranslated-.txt. */ - public boolean dumpUntranslated = true; +public class ModConfig extends Config { + /** Order must match the "Language" dropdown values below. */ + private static final String[] LANGUAGE_CODES = { + "ru_ru", "uk_ua", "de_de", "fr_fr", "es_es", "pt_br", "pl_pl", "tr_tr", "zh_cn", "ja_jp", "ko_kr" + }; + + @Expose + @Category(name = "General", desc = "Master switch, language and where to translate") + public General general = new General(); + + @Expose + @Category(name = "Surfaces", desc = "What gets translated") + public Surfaces surfaces = new Surfaces(); + + @Expose + @Category(name = "Online Translation", desc = "Free machine translation for unknown text") + public Online online = new Online(); + /** Reserved for Phase 3 (premium cloud translation). */ + @Expose public String premiumKey = ""; - public static ModConfig get() { - if (instance == null) { - instance = load(); - } - return instance; + public static class General { + @Expose + @ConfigOption(name = "Enable Translation", desc = "Master switch for the whole mod.") + @ConfigEditorBoolean + public boolean enabled = true; + + @Expose + @ConfigOption(name = "Language", desc = "Target language. Russian ships with a curated dictionary; other languages rely on online translation.") + @ConfigEditorDropdown(values = { + "Русский", "Українська", "Deutsch", "Français", "Español", + "Português (BR)", "Polski", "Türkçe", "中文", "日本語", "한국어" + }) + public int languageIndex = 0; + + @Expose + @ConfigOption(name = "Only on Hypixel", desc = "Translate only while connected to Hypixel. Disable to test in singleplayer.") + @ConfigEditorBoolean + public boolean onlyOnHypixel = true; + + @ConfigOption(name = "Translation Editor", desc = "Edit the most recent strings by hand. Your edits become overrides and always win over the dictionary and the machine translation.") + @ConfigEditorButton(runnableId = ModConfig.OPEN_EDITOR_RUNNABLE, buttonText = "OPEN") + public int openEditor = 0; } - public static Path directory() { - return FabricLoader.getInstance().getConfigDir().resolve("skyblock-translator"); + public static class Surfaces { + @Expose + @ConfigOption(name = "Item Tooltips", desc = "Item names and lore in container screens.") + @ConfigEditorBoolean + public boolean items = true; + + @Expose + @ConfigOption(name = "Menu Titles", desc = "Container titles and labels.") + @ConfigEditorBoolean + public boolean menus = true; + + @Expose + @ConfigOption(name = "NPC Dialogues & System Messages", desc = "Server messages in chat: dialogues, quests, announcements.") + @ConfigEditorBoolean + public boolean dialogs = true; + + @Expose + @ConfigOption(name = "Player Chat", desc = "Messages written by other players.") + @ConfigEditorBoolean + public boolean playerChat = false; + + @Expose + @ConfigOption(name = "Scoreboard Sidebar", desc = "The SKYBLOCK sidebar on the right.") + @ConfigEditorBoolean + public boolean scoreboard = true; + + @Expose + @ConfigOption(name = "Tab List", desc = "Tab entries, header and footer. Player names are never sent to an online translator.") + @ConfigEditorBoolean + public boolean tabList = true; + + @Expose + @ConfigOption(name = "Boss Bar", desc = "Event and objective banners at the top of the screen.") + @ConfigEditorBoolean + public boolean bossBar = true; } - private static Path file() { - return directory().resolve("config.json"); + public static class Online { + @Expose + @ConfigOption(name = "Online Translation", desc = "Translate unknown strings through an online service and cache the result locally. With this off, only dictionaries and the cache are used.") + @ConfigEditorBoolean + public boolean translateOnline = true; + + @Expose + @ConfigOption(name = "Provider", desc = "Google — free, works out of the box. LibreTranslate — your own instance (URL below).") + @ConfigEditorDropdown(values = {"Google (free)", "LibreTranslate"}) + public int providerIndex = 0; + + @Expose + @ConfigOption(name = "Daily Request Limit", desc = "Maximum online translation requests per day. Cached translations don't count.") + @ConfigEditorSlider(minValue = 0, maxValue = 100000, minStep = 500) + public int dailyBudget = 2000; + + @Expose + @ConfigOption(name = "LibreTranslate URL", desc = "Instance address, e.g. https://libretranslate.example.com") + @ConfigEditorText + public String libreUrl = ""; + + @Expose + @ConfigOption(name = "LibreTranslate API Key", desc = "Leave empty if the instance doesn't require one.") + @ConfigEditorText + public String libreApiKey = ""; + + @Expose + @ConfigOption(name = "Collect Untranslated Strings", desc = "Append unknown templates to untranslated-.txt — used to grow the bundled dictionaries.") + @ConfigEditorBoolean + public boolean dumpUntranslated = true; } - private static ModConfig load() { - Path file = file(); - if (Files.exists(file)) { - try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { - ModConfig loaded = GSON.fromJson(reader, ModConfig.class); - if (loaded != null) { - return loaded; - } - } catch (IOException | RuntimeException e) { - SkyblockTranslatorClient.LOGGER.error("Failed to load config, using defaults", e); - } + static final int OPEN_EDITOR_RUNNABLE = 0; + + @Override + public void executeRunnable(int runnableId) { + if (runnableId == OPEN_EDITOR_RUNNABLE) { + Minecraft.getInstance().setScreenAndShow(ConfigScreens.createEditor(null)); } - ModConfig fresh = new ModConfig(); - fresh.save(); - return fresh; + } + + public String languageCode() { + int idx = general.languageIndex; + return LANGUAGE_CODES[idx >= 0 && idx < LANGUAGE_CODES.length ? idx : 0]; + } + + public String onlineProviderCode() { + return online.providerIndex == 1 ? "libretranslate" : "google"; + } + + public static ModConfig get() { + return ConfigHolder.get(); + } + + public static Path directory() { + return FabricLoader.getInstance().getConfigDir().resolve("skyblock-translator"); } public void save() { - try { - Files.createDirectories(directory()); - try (Writer writer = Files.newBufferedWriter(file(), StandardCharsets.UTF_8)) { - GSON.toJson(this, writer); - } - } catch (IOException e) { - SkyblockTranslatorClient.LOGGER.error("Failed to save config", e); - } + ConfigHolder.save(); } } diff --git a/src/main/java/dev/laybalt/skyblocktranslator/detect/HypixelDetector.java b/src/main/java/dev/laybalt/skyblocktranslator/detect/HypixelDetector.java index bfbe502..07452b0 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/detect/HypixelDetector.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/detect/HypixelDetector.java @@ -20,10 +20,10 @@ private HypixelDetector() { public static boolean isTranslationActive() { ModConfig config = ModConfig.get(); - if (!config.enabled) { + if (!config.general.enabled) { return false; } - if (!config.onlyOnHypixel) { + if (!config.general.onlyOnHypixel) { return true; } ServerData server = Minecraft.getInstance().getCurrentServer(); diff --git a/src/main/java/dev/laybalt/skyblocktranslator/mixin/AbstractContainerScreenMixin.java b/src/main/java/dev/laybalt/skyblocktranslator/mixin/AbstractContainerScreenMixin.java index 46319ff..fa02be9 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/mixin/AbstractContainerScreenMixin.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/mixin/AbstractContainerScreenMixin.java @@ -32,7 +32,7 @@ public abstract class AbstractContainerScreenMixin { @Inject(method = "getTooltipFromContainerItem", at = @At("RETURN"), cancellable = true) private void skyblockTranslator$translateTooltip(ItemStack stack, CallbackInfoReturnable> cir) { - if (!ModConfig.get().translateItems || !HypixelDetector.isTranslationActive()) { + if (!ModConfig.get().surfaces.items || !HypixelDetector.isTranslationActive()) { return; } List lines = cir.getReturnValue(); @@ -51,7 +51,7 @@ public abstract class AbstractContainerScreenMixin { index = 1 ) private Component skyblockTranslator$translateLabel(Component original) { - if (!ModConfig.get().translateMenus || !HypixelDetector.isTranslationActive()) { + if (!ModConfig.get().surfaces.menus || !HypixelDetector.isTranslationActive()) { return original; } return TranslationEngine.get().translate(original); diff --git a/src/main/java/dev/laybalt/skyblocktranslator/mixin/BossHealthOverlayMixin.java b/src/main/java/dev/laybalt/skyblocktranslator/mixin/BossHealthOverlayMixin.java index 7dfd59d..3ed196a 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/mixin/BossHealthOverlayMixin.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/mixin/BossHealthOverlayMixin.java @@ -22,7 +22,7 @@ public abstract class BossHealthOverlayMixin { index = 1 ) private Component skyblockTranslator$translateBossBar(Component original) { - if (!ModConfig.get().translateBossBar || !HypixelDetector.isTranslationActive()) { + if (!ModConfig.get().surfaces.bossBar || !HypixelDetector.isTranslationActive()) { return original; } return TranslationEngine.get().translate(original); diff --git a/src/main/java/dev/laybalt/skyblocktranslator/mixin/ChatComponentMixin.java b/src/main/java/dev/laybalt/skyblocktranslator/mixin/ChatComponentMixin.java index a0b8a55..40f5aa6 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/mixin/ChatComponentMixin.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/mixin/ChatComponentMixin.java @@ -23,7 +23,7 @@ public abstract class ChatComponentMixin { @ModifyVariable(method = "addServerSystemMessage", at = @At("HEAD"), argsOnly = true) private Component skyblockTranslator$translateSystem(Component message) { - if (!ModConfig.get().translateDialogs || !HypixelDetector.isTranslationActive() + if (!ModConfig.get().surfaces.dialogs || !HypixelDetector.isTranslationActive() || hasInteraction(message)) { return message; } @@ -32,7 +32,7 @@ public abstract class ChatComponentMixin { @ModifyVariable(method = "addPlayerMessage", at = @At("HEAD"), argsOnly = true) private Component skyblockTranslator$translatePlayer(Component message) { - if (!ModConfig.get().translatePlayerChat || !HypixelDetector.isTranslationActive() + if (!ModConfig.get().surfaces.playerChat || !HypixelDetector.isTranslationActive() || hasInteraction(message)) { return message; } diff --git a/src/main/java/dev/laybalt/skyblocktranslator/mixin/HudMixin.java b/src/main/java/dev/laybalt/skyblocktranslator/mixin/HudMixin.java index d82d65c..694c2c3 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/mixin/HudMixin.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/mixin/HudMixin.java @@ -28,7 +28,7 @@ public abstract class HudMixin { index = 1 ) private Component skyblockTranslator$translateSidebar(Component original) { - if (!ModConfig.get().translateScoreboard || !HypixelDetector.isTranslationActive()) { + if (!ModConfig.get().surfaces.scoreboard || !HypixelDetector.isTranslationActive()) { return original; } return TranslationEngine.get().translate(original); diff --git a/src/main/java/dev/laybalt/skyblocktranslator/mixin/PlayerTabOverlayMixin.java b/src/main/java/dev/laybalt/skyblocktranslator/mixin/PlayerTabOverlayMixin.java index a4ff372..78ab864 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/mixin/PlayerTabOverlayMixin.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/mixin/PlayerTabOverlayMixin.java @@ -24,7 +24,7 @@ public abstract class PlayerTabOverlayMixin { @Inject(method = "getNameForDisplay", at = @At("RETURN"), cancellable = true) private void skyblockTranslator$translateEntry(PlayerInfo info, CallbackInfoReturnable cir) { - if (!ModConfig.get().translateTabList || !HypixelDetector.isTranslationActive()) { + if (!ModConfig.get().surfaces.tabList || !HypixelDetector.isTranslationActive()) { return; } Component original = cir.getReturnValue(); @@ -45,7 +45,7 @@ public abstract class PlayerTabOverlayMixin { } private static Component translateInfo(Component component) { - if (component == null || !ModConfig.get().translateTabList || !HypixelDetector.isTranslationActive()) { + if (component == null || !ModConfig.get().surfaces.tabList || !HypixelDetector.isTranslationActive()) { return component; } return TranslationEngine.get().translate(component); diff --git a/src/main/java/dev/laybalt/skyblocktranslator/pipeline/TranslationEngine.java b/src/main/java/dev/laybalt/skyblocktranslator/pipeline/TranslationEngine.java index 6fd4176..f2466f2 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/pipeline/TranslationEngine.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/pipeline/TranslationEngine.java @@ -71,7 +71,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { private final Path dumpFile; private TranslationEngine(ModConfig config) { - String language = config.language; + String language = config.languageCode(); this.overrides = new OverrideProvider(ModConfig.directory(), language); this.cache = new LocalCacheProvider(ModConfig.directory(), language); this.providers = List.of(overrides, new DictionaryProvider(language), cache); @@ -83,15 +83,15 @@ private TranslationEngine(ModConfig config) { SkyblockTranslatorClient.LOGGER.warn("Could not read {}", dumpFile, e); } } - this.remoteQueue = config.translateOnline - ? new RemoteQueue(createTranslator(config), mtLang(language), config.dailyOnlineBudget, + this.remoteQueue = config.online.translateOnline + ? new RemoteQueue(createTranslator(config), mtLang(language), config.online.dailyBudget, ModConfig.directory(), this::onRemoteResult) : null; } private static RemoteTranslator createTranslator(ModConfig config) { - if ("libretranslate".equals(config.onlineProvider) && !config.libreTranslateUrl.isBlank()) { - return new LibreTranslator(config.libreTranslateUrl, config.libreTranslateApiKey); + if ("libretranslate".equals(config.onlineProviderCode()) && !config.online.libreUrl.isBlank()) { + return new LibreTranslator(config.online.libreUrl, config.online.libreApiKey); } return new GoogleFreeTranslator(); } @@ -293,7 +293,7 @@ private void remember(String templateKey, @Nullable String translation) { } private void recordMissing(String templateKey) { - if (!ModConfig.get().dumpUntranslated || !hasLetters(templateKey) || !dumped.add(templateKey)) { + if (!ModConfig.get().online.dumpUntranslated || !hasLetters(templateKey) || !dumped.add(templateKey)) { return; } try { diff --git a/src/main/java/dev/laybalt/skyblocktranslator/ui/ConfigScreens.java b/src/main/java/dev/laybalt/skyblocktranslator/ui/ConfigScreens.java index cda4e2e..9e25dee 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/ui/ConfigScreens.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/ui/ConfigScreens.java @@ -7,86 +7,29 @@ import dev.isxander.yacl3.api.ConfigCategory; import dev.isxander.yacl3.api.Option; import dev.isxander.yacl3.api.OptionDescription; -import dev.isxander.yacl3.api.OptionGroup; import dev.isxander.yacl3.api.YetAnotherConfigLib; import dev.isxander.yacl3.api.controller.BooleanControllerBuilder; -import dev.isxander.yacl3.api.controller.DropdownStringControllerBuilder; -import dev.isxander.yacl3.api.controller.IntegerFieldControllerBuilder; import dev.isxander.yacl3.api.controller.StringControllerBuilder; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; -import dev.laybalt.skyblocktranslator.config.ModConfig; import dev.laybalt.skyblocktranslator.pipeline.TranslationEngine; /** - * The in-game settings UI (YACL): general toggles, language, online translation - * backend, and a live string editor over the most recently seen templates — - * edits are saved as user overrides that beat the dictionary and the MT cache. + * The in-game translation editor (YACL): the most recently seen strings, + * editable in place. Saved edits become user overrides that beat both the + * bundled dictionary and the MT cache. + * + *

The main settings live in the MoulConfig screen ({@code ConfigHolder.openGui()}); + * this screen is reached through its "Translation Editor" button. */ public final class ConfigScreens { - private static final List LANGUAGES = List.of( - "ru_ru", "uk_ua", "de_de", "fr_fr", "es_es", "pt_br", "pl_pl", "tr_tr", "zh_cn", "ja_jp", "ko_kr"); - private static final List PROVIDERS = List.of("google", "libretranslate"); - private ConfigScreens() { } - public static Screen create(Screen parent) { - ModConfig config = ModConfig.get(); + public static Screen createEditor(Screen parent) { Map editedOverrides = new LinkedHashMap<>(); - var general = ConfigCategory.createBuilder() - .name(Component.translatable("sbt.config.category.general")) - .option(bool("sbt.config.enabled", true, () -> config.enabled, v -> config.enabled = v)) - .option(Option.createBuilder() - .name(Component.translatable("sbt.config.language")) - .description(OptionDescription.of(Component.translatable("sbt.config.language.desc"))) - .binding("ru_ru", () -> config.language, v -> config.language = v) - .controller(opt -> DropdownStringControllerBuilder.create(opt).values(LANGUAGES)) - .build()) - .option(bool("sbt.config.onlyOnHypixel", true, () -> config.onlyOnHypixel, v -> config.onlyOnHypixel = v)) - .group(OptionGroup.createBuilder() - .name(Component.translatable("sbt.config.group.categories")) - .option(bool("sbt.config.translateItems", true, () -> config.translateItems, v -> config.translateItems = v)) - .option(bool("sbt.config.translateMenus", true, () -> config.translateMenus, v -> config.translateMenus = v)) - .option(bool("sbt.config.translateDialogs", true, () -> config.translateDialogs, v -> config.translateDialogs = v)) - .option(bool("sbt.config.translatePlayerChat", false, () -> config.translatePlayerChat, v -> config.translatePlayerChat = v)) - .option(bool("sbt.config.translateScoreboard", true, () -> config.translateScoreboard, v -> config.translateScoreboard = v)) - .option(bool("sbt.config.translateTabList", true, () -> config.translateTabList, v -> config.translateTabList = v)) - .option(bool("sbt.config.translateBossBar", true, () -> config.translateBossBar, v -> config.translateBossBar = v)) - .build()) - .build(); - - var online = ConfigCategory.createBuilder() - .name(Component.translatable("sbt.config.category.online")) - .option(bool("sbt.config.translateOnline", true, () -> config.translateOnline, v -> config.translateOnline = v)) - .option(Option.createBuilder() - .name(Component.translatable("sbt.config.onlineProvider")) - .description(OptionDescription.of(Component.translatable("sbt.config.onlineProvider.desc"))) - .binding("google", () -> config.onlineProvider, v -> config.onlineProvider = v) - .controller(opt -> DropdownStringControllerBuilder.create(opt).values(PROVIDERS)) - .build()) - .option(Option.createBuilder() - .name(Component.translatable("sbt.config.dailyOnlineBudget")) - .description(OptionDescription.of(Component.translatable("sbt.config.dailyOnlineBudget.desc"))) - .binding(2000, () -> config.dailyOnlineBudget, v -> config.dailyOnlineBudget = v) - .controller(opt -> IntegerFieldControllerBuilder.create(opt).range(0, 100_000)) - .build()) - .option(Option.createBuilder() - .name(Component.translatable("sbt.config.libreUrl")) - .description(OptionDescription.of(Component.translatable("sbt.config.libreUrl.desc"))) - .binding("", () -> config.libreTranslateUrl, v -> config.libreTranslateUrl = v) - .controller(StringControllerBuilder::create) - .build()) - .option(Option.createBuilder() - .name(Component.translatable("sbt.config.libreApiKey")) - .description(OptionDescription.of(Component.translatable("sbt.config.libreApiKey.desc"))) - .binding("", () -> config.libreTranslateApiKey, v -> config.libreTranslateApiKey = v) - .controller(StringControllerBuilder::create) - .build()) - .build(); - var editorBuilder = ConfigCategory.createBuilder() .name(Component.translatable("sbt.config.category.editor")); List> recent = TranslationEngine.get().recentSeenSnapshot(); @@ -118,14 +61,8 @@ public static Screen create(Screen parent) { return YetAnotherConfigLib.createBuilder() .title(Component.translatable("sbt.config.title")) - .category(general) - .category(online) .category(editorBuilder.build()) - .save(() -> { - config.save(); - applyOverrides(editedOverrides); - TranslationEngine.reload(); - }) + .save(() -> applyOverrides(editedOverrides)) .build() .generateScreen(parent); } @@ -134,19 +71,10 @@ private static void applyOverrides(Map edited) { if (edited.isEmpty()) { return; } - var overrides = TranslationEngine.get().overrides(); - edited.forEach(overrides::put); - overrides.save(); - } - - private static Option bool(String key, boolean defaultValue, - java.util.function.Supplier getter, java.util.function.Consumer setter) { - return Option.createBuilder() - .name(Component.translatable(key)) - .description(OptionDescription.of(Component.translatable(key + ".desc"))) - .binding(defaultValue, getter::get, setter::accept) - .controller(opt -> BooleanControllerBuilder.create(opt).yesNoFormatter()) - .build(); + var engine = TranslationEngine.get(); + edited.forEach(engine.overrides()::put); + engine.overrides().save(); + engine.flushMemo(); } private static String shorten(String template) { diff --git a/src/main/java/dev/laybalt/skyblocktranslator/ui/ModMenuIntegration.java b/src/main/java/dev/laybalt/skyblocktranslator/ui/ModMenuIntegration.java index 4e1443c..f2acb2c 100644 --- a/src/main/java/dev/laybalt/skyblocktranslator/ui/ModMenuIntegration.java +++ b/src/main/java/dev/laybalt/skyblocktranslator/ui/ModMenuIntegration.java @@ -2,11 +2,20 @@ import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; +import io.github.notenoughupdates.moulconfig.gui.GuiContext; +import io.github.notenoughupdates.moulconfig.gui.GuiElementComponent; +import io.github.notenoughupdates.moulconfig.platform.MoulConfigScreenComponent; +import net.minecraft.network.chat.Component; -/** Puts the settings screen behind Mod Menu's "Configure" button. */ +import dev.laybalt.skyblocktranslator.config.ConfigHolder; + +/** Puts the MoulConfig settings screen behind Mod Menu's "Configure" button. */ public class ModMenuIntegration implements ModMenuApi { @Override public ConfigScreenFactory getModConfigScreenFactory() { - return ConfigScreens::create; + return parent -> new MoulConfigScreenComponent( + Component.literal("SkyBlock Translator"), + new GuiContext(new GuiElementComponent(ConfigHolder.managed().getEditor())), + parent); } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d540f97..21a7c79 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -31,6 +31,7 @@ "minecraft": "~26.2", "java": ">=25", "fabric-api": "*", + "fabric-language-kotlin": "*", "yet_another_config_lib_v3": "*" }, "suggests": {