diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java index 8c5a56b..5311ae8 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java @@ -13,27 +13,35 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.GraphicsEnvironment; +import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static io.github.gaming32.modloadingscreen.MlsTransformers.ACTUAL_LOADING_SCREEN; @@ -64,7 +72,67 @@ public class ActualLoadingScreen { private static boolean enableMemoryDisplay = true; + // Cache holding loaded translation key-value mappings + private static final Map TRANSLATIONS = new HashMap<>(); + + /** + * Loads translations for the current environment. + * Always loads default English (en_us) first, then overlays the system locale if different. + */ + public static void loadTranslations() { + TRANSLATIONS.clear(); + loadLangFile("en_us"); + final String locale = Locale.getDefault().toString().toLowerCase(Locale.ROOT); + if (!locale.equals("en_us")) { + loadLangFile(locale); + } + } + + /** + * Parses a flat key-value JSON file using regex to avoid external library dependencies + * during early bootstrapping. + */ + private static void loadLangFile(String langCode) { + final InputStream is = ClassLoader.getSystemResourceAsStream("assets/mod-loading-screen/lang/" + langCode + ".json"); + if (is == null) return; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + final Pattern pattern = Pattern.compile("^\\s*\"([^\"]+)\"\\s*:\\s*\"(.*)\",?\\s*$"); + String line; + while ((line = reader.readLine()) != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.matches()) { + String val = matcher.group(2).replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n"); + TRANSLATIONS.put(matcher.group(1), val); + } + } + } catch (Exception ignored) { + } + } + + /** + * Translates a given translation key with optional formatting arguments. + * Falls back to MessageFormat or the raw translation key if String.format fails. + */ + public static String translate(String key, Object... args) { + String pattern = TRANSLATIONS.getOrDefault(key, key); + if (args == null || args.length == 0) { + return pattern; + } + try { + return String.format(pattern, args); + } catch (Exception e) { + try { + return MessageFormat.format(pattern, args); + } catch (Exception ex) { + return pattern; + } + } + } + public static void startLoadingScreen(boolean fabricReady) { + // Ensure localization mappings are loaded before initializing the UI + loadTranslations(); + final Path gameDir = fabricReady ? FabricLoader.getInstance().getGameDir() : Paths.get(".").toAbsolutePath(); final Path runDir = gameDir.resolve(".cache/mod-loading-screen"); @@ -176,7 +244,8 @@ public static void startLoadingScreen(boolean fabricReady) { if (fabricReady) { setFabricTitle(); } else { - dialog.setTitle(runningOnQuilt ? "Loading Quilt Loader" : "Loading Fabric Loader"); + // Apply localized loader window title before mod discovery finishes + dialog.setTitle(translate(runningOnQuilt ? "modloadingscreen.title.loading_quilt" : "modloadingscreen.title.loading_fabric")); } dialog.setResizable(false); @@ -279,7 +348,8 @@ private static void startMemoryThread() { public static void setTitleFromMetadata(String id, String name, String version) { if (titleSet || IGNORED_BUILTIN.contains(id)) return; titleSet = true; - setTitle("Loading " + name + ' ' + version); + // Use localized title with mod name and version placeholders + setTitle(translate("modloadingscreen.title.loading_mod", name, version)); } private static void setFabricTitle() { @@ -488,17 +558,19 @@ private static void updateMemoryUsage0(long usage, long total) { memoryBar.setMaximum(totalMb); memoryBar.setValue(usageMb); - memoryBar.setString(usageMb + " MB / " + totalMb + " MB"); + // Format memory status using localized pattern + memoryBar.setString(translate("modloadingscreen.memory.usage", usageMb, totalMb)); } private static void setLabel(JProgressBar progressBar, String typeName, String typeType, @Nullable String modName) { - final StringBuilder message = new StringBuilder("Loading '").append(typeName) - .append("' (").append(typeType).append(") \u2014 ") - .append(progressBar.getValue()).append('/').append(progressBar.getMaximum()); + final int current = progressBar.getValue(); + final int max = progressBar.getMaximum(); + // Update progress bar string with localized status pattern if (modName != null) { - message.append(" \u2014 ").append(modName); + progressBar.setString(translate("modloadingscreen.entrypoint.status_mod", typeName, typeType, current, max, modName)); + } else { + progressBar.setString(translate("modloadingscreen.entrypoint.status", typeName, typeType, current, max)); } - progressBar.setString(message.toString()); } private static void println(String message) { @@ -591,11 +663,12 @@ public static void main(String[] args) { println("IPC client exiting cleanly"); } catch (Exception e) { println("Error in IPC client", e); + // Display localized error dialog in case of an IPC client failure JOptionPane.showMessageDialog( - dialog, "An error occurred in Mod Loading Screen's IPC client\n" + e, - "Mod Loading Screen IPC Client", JOptionPane.ERROR_MESSAGE + dialog, translate("modloadingscreen.error.ipc_client", e), + translate("modloadingscreen.error.ipc_client_title"), JOptionPane.ERROR_MESSAGE ); } close(); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java index 06fd58b..a9e8c7f 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java @@ -11,26 +11,31 @@ public class EarlyLoadingAgent { public static void premain(String args, Instrumentation instrumentation) throws IOException { - System.out.println("[ModLoadingScreen] I just want to say... I'm loading *really* **extremely** early."); + // Output localized startup log + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.early_loading")); System.setProperty("mod-loading-screen.loaded", "true"); final Path flatlafDestPath = Paths.get(".cache/mod-loading-screen/flatlaf.jar").toAbsolutePath(); Files.createDirectories(flatlafDestPath.getParent()); try (InputStream is = EarlyLoadingAgent.class.getClassLoader().getResourceAsStream(MlsConstants.FLATLAF_PATH)) { if (is == null) { - System.err.println("[ModLoadingScreen] [ERROR] flatlaf.jar not found! Aborting."); + // Log localized error if FlatLaf library cannot be found + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.flatlaf_not_found")); return; } Files.copy(is, flatlafDestPath, StandardCopyOption.REPLACE_EXISTING); } - System.out.println("[ModLoadingScreen] Extracted flatlaf.jar"); + // Log localized confirmation after FlatLaf jar is extracted + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.extracted_flatlaf")); instrumentation.appendToSystemClassLoaderSearch(new JarFile(flatlafDestPath.toFile())); + // Start loading screen in early agent phase (fabricReady = false) ActualLoadingScreen.startLoadingScreen(false); + // Register transformer to hook into Fabric/Quilt early entrypoints instrumentation.addTransformer( (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> MlsTransformers.instrumentClass(className, classfileBuffer), false ); } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java index 5f37e3f..14cbf32 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java @@ -45,6 +45,7 @@ public final class MlsTransformers { public static final Map>> TRANSFORMERS; static { + // Map target classes to their respective bytecode transformation consumers final Map>> transformers = new HashMap<>(8); transformers.put(FABRIC_LOADER_IMPL, Collections.singleton( MlsTransformers::instrumentFabricLoaderImplInvokeEntrypoints @@ -70,11 +71,12 @@ public final class MlsTransformers { } static byte[] instrumentClass(String name, byte[] bytes) { + // Handle debug dump initialization when enabled if (DUMP_TRANSFORMED_CLASSES && !notifiedClassDump) { synchronized (MlsTransformers.class) { if (!notifiedClassDump) { notifiedClassDump = true; - System.out.println("[ModLoadingScreen] Transformed class dumping is active"); + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_active")); try { final Path dumpDir = Paths.get(".mlsDebugDump"); if (Files.isDirectory(dumpDir)) { @@ -93,7 +95,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx }); } } catch (Throwable t) { - System.err.println("[ModLoadingScreen] [ERROR] Failed to clear debug dump dir"); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_clear_failed")); } } } @@ -102,7 +104,8 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx try { final Collection> transformer = TRANSFORMERS.get(name); if (transformer != null) { - System.out.println("[ModLoadingScreen] Transforming " + name); + // Log class transformation using localized message + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.transforming", name)); final ClassReader reader = new ClassReader(bytes); final ClassNode clazz = new ClassNode(); reader.accept(clazz, 0); @@ -110,7 +113,8 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx try { part.accept(clazz); } catch (Exception e) { - System.err.println("[ModLoadingScreen] [ERROR] Transformer " + part + " for " + name + " failed"); + // Log individual transformer step failure + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transformer_failed", part, name)); e.printStackTrace(); } } @@ -123,14 +127,15 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx Files.createDirectories(dumpedPath.getParent()); Files.write(dumpedPath, result); } catch (Exception e) { - System.err.println("[ModLoadingScreen] [ERROR] Failed to dump class " + name); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dump_failed", name)); e.printStackTrace(); } } return result; } } catch (Throwable t) { - System.err.println("[ModLoadingScreen] [ERROR] Completely failed to transform " + name); + // Log catastrophic transformation failure + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transform_failed", name)); t.printStackTrace(); } return null; @@ -142,11 +147,13 @@ private static void instrumentFabricLoaderImplInvokeEntrypoints(ClassNode clazz) .findFirst() .orElse(null); if (method == null) { - System.out.println("[ModLoadingScreen] New-style FabricLoaderImpl.invokeEntrypoints not found. Assuming old Fabric."); + // Log fallback when invokeEntrypoints method structure does not match newer FabricLoader versions + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.old_fabric_fallback")); return; } final ListIterator it = method.instructions.iterator(); + // Inject check before entrypoint invocation to verify if loading screen should close maybeCloseAfter(it, true); while (it.hasNext()) { @@ -155,7 +162,8 @@ private static void instrumentFabricLoaderImplInvokeEntrypoints(ClassNode clazz) if (insn.getOpcode() == Opcodes.ACONST_NULL) break; } it.previous(); - mainEntrypointHooks(it, false, true); + // Insert entrypoint lifecycle tracking hooks + mainEntrypointHooks(it, false, true); maybeCloseAfter(it, true); } @@ -170,10 +178,8 @@ private static void instrumentEntrypointUtilsInvoke(ClassNode clazz, boolean onQ maybeCloseAfter(it, false); } - /** - * Inserts code for calling maybeCloseAfter, and leaves {@code it} pointing to the {@code RETURN}. - */ private static void maybeCloseAfter(ListIterator it, boolean instanceMethod) { + // Locate return opcode to insert closing check right before method exits while (it.hasNext()) { final AbstractInsnNode insn = it.next(); if (!(insn instanceof InsnNode)) continue; @@ -201,10 +207,10 @@ private static void instrumentEntrypointUtilsInvoke0(ClassNode clazz, boolean on private static void mainEntrypointHooks(ListIterator it, boolean onQuilt, boolean instanceMethod) { final int varOffset = instanceMethod ? 1 : 0; - //noinspection PointlessArithmeticExpression final int keyIndex = 0 + varOffset; final int typeIndex = 1 + varOffset; + // Hook beforeEntrypointType at the start of entrypoint type processing it.add(new VarInsnNode(Opcodes.ALOAD, keyIndex)); it.add(new VarInsnNode(Opcodes.ALOAD, typeIndex)); it.add(new MethodInsnNode( @@ -213,6 +219,7 @@ private static void mainEntrypointHooks(ListIterator it, boole "(Ljava/lang/String;Ljava/lang/Class;)V" )); + // Locate storage opcode of entrypoint container variable to hook single entrypoint executions final int container = (onQuilt ? 7 : 6) + varOffset; while (it.hasNext()) { final AbstractInsnNode insn = it.next(); @@ -267,6 +274,7 @@ private static void mainEntrypointHooks(ListIterator it, boole false )); + // Locate loop boundary to place afterEntrypointType callback while (it.hasNext()) { final AbstractInsnNode insn = it.next(); if (!(insn instanceof InsnNode)) continue; @@ -291,38 +299,27 @@ private static void instrumentModDiscovererDiscoverMods(ClassNode clazz, boolean final String BuiltinMod = onQuilt ? QUILT_BUILTIN_MOD : FABRIC_BUILTIN_MOD; + // Intercept builtin mod discovery to extract title metadata while (it.hasNext()) { final AbstractInsnNode insn = it.next(); if (!(insn instanceof TypeInsnNode)) continue; if (insn.getOpcode() == Opcodes.CHECKCAST && ((TypeInsnNode)insn).desc.equals(BuiltinMod)) break; } - // BuiltinMod it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod BuiltinMod it.add(new FieldInsnNode(Opcodes.GETFIELD, BuiltinMod, "metadata", "L" + MOD_METADATA + ";")); - // BuiltinMod ModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod ModMetadata ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getId", "()Ljava/lang/String;")); - // BuiltinMod ModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String ModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod String ModMetadata ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getName", "()Ljava/lang/String;")); - // BuiltinMod String ModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String String ModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, MOD_METADATA, "getVersion", "()L" + FABRIC_VERSION + ";")); - // BuiltinMod String String Version it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, FABRIC_VERSION, "getFriendlyString", "()Ljava/lang/String;")); - // BuiltinMod String String String it.add(new MethodInsnNode( Opcodes.INVOKESTATIC, ACTUAL_LOADING_SCREEN, "setTitleFromMetadata", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" )); - // BuiltinMod } private static void instrumentStandardQuiltPluginAddBuiltinMods(ClassNode clazz) { @@ -332,37 +329,26 @@ private static void instrumentStandardQuiltPluginAddBuiltinMods(ClassNode clazz) .orElseThrow(IllegalStateException::new); final ListIterator it = method.instructions.iterator(); + // Intercept Quilt builtin mod registration to extract title metadata while (it.hasNext()) { final AbstractInsnNode insn = it.next(); if (!(insn instanceof TypeInsnNode)) continue; if (insn.getOpcode() == Opcodes.CHECKCAST && ((TypeInsnNode)insn).desc.equals(QUILT_BUILTIN_MOD)) break; } - // BuiltinMod it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod BuiltinMod it.add(new FieldInsnNode(Opcodes.GETFIELD, QUILT_BUILTIN_MOD, "metadata", "L" + INTERNAL_MOD_METADATA + ";")); - // BuiltinMod InternalModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod InternalModMetadata InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "id", "()Ljava/lang/String;")); - // BuiltinMod InternalModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String InternalModMetadata it.add(new InsnNode(Opcodes.DUP)); - // BuiltinMod String InternalModMetadata InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "name", "()Ljava/lang/String;")); - // BuiltinMod String InternalModMetadata String it.add(new InsnNode(Opcodes.SWAP)); - // BuiltinMod String String InternalModMetadata it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, INTERNAL_MOD_METADATA, "version", "()L" + QUILT_VERSION + ";")); - // BuiltinMod String String Version it.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, QUILT_VERSION, "raw", "()Ljava/lang/String;")); - // BuiltinMod String String String it.add(new MethodInsnNode( Opcodes.INVOKESTATIC, ACTUAL_LOADING_SCREEN, "setTitleFromMetadata", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" )); - // BuiltinMod } -} +} \ No newline at end of file diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java index 68700fd..7d14fcc 100644 --- a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java +++ b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java @@ -27,19 +27,23 @@ public class ModLoadingScreen implements LanguageAdapter { @SuppressWarnings("unchecked") public T create(ModContainer mod, String value, Class type) throws LanguageAdapterException { if (type != PreLaunchEntrypoint.class) { - throw new LanguageAdapterException("Fake entrypoint only supported on PreLaunchEntrypoint"); + // Throw localized exception if an unsupported entrypoint type is requested + throw new LanguageAdapterException(ActualLoadingScreen.translate("modloadingscreen.error.fake_entrypoint_unsupported")); } return (T)(PreLaunchEntrypoint)() -> {}; } public static void init() throws Throwable { - System.out.println("[ModLoadingScreen] I just want to say... I'm loading *really* early."); + // Log localized banner for early mod loading phase + System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.mod_loading_early")); if (System.setProperty("mod-loading-screen.loaded", "true") != null) { - System.err.println("[ModLoadingScreen] [WARN] Mod Loading Screen installed as both a mod and an agent."); - System.err.println("[ModLoadingScreen] [WARN] Please avoid doing this. To avoid issues, the mod has disabled itself."); + // Warn and disable if both agent and mod versions are installed concurrently + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn1")); + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn2")); return; } + // Add mod and FlatLaf jar roots to the system class loader search path ClassLoaders.addToSystemClassPath( FabricLoader.getInstance() .getModContainer("mod-loading-screen") @@ -55,6 +59,7 @@ public static void init() throws Throwable { .toUri().toURL() ); + // Load ActualLoadingScreen class definition into the system class loader final byte[] alsData = Files.readAllBytes( FabricLoader.getInstance() .getModContainer("mod-loading-screen") @@ -63,11 +68,13 @@ public static void init() throws Throwable { .orElseThrow(AssertionError::new) ); + // Invoke startLoadingScreen with fabricReady set to true Methods.invoke(null, Methods.getDeclaredMethod( ClassLoaders.defineClass(ClassLoader.getSystemClassLoader(), ACTUAL_LOADING_SCREEN.replace('/', '.'), alsData), "startLoadingScreen", boolean.class ), true); + // Register transformer and retransform already-loaded classes final Instrumentation instrumentation = Agents.getInstrumentation(); instrumentation.addTransformer( (loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> @@ -87,8 +94,9 @@ public static void init() throws Throwable { try { init(); } catch (Throwable t) { - System.err.println("[ModLoadingScreen] Failed to initialize loading screen. Aborting!"); + // Log localized error message when initialization fails + System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.init_failed")); throw new Error(t); } } -} +} \ No newline at end of file diff --git a/src/main/resources/assets/mod-loading-screen/lang/en_us.json b/src/main/resources/assets/mod-loading-screen/lang/en_us.json new file mode 100644 index 0000000..1bc9ac6 --- /dev/null +++ b/src/main/resources/assets/mod-loading-screen/lang/en_us.json @@ -0,0 +1,32 @@ +{ + "modmenu.nameTranslation.mod-loading-screen": "Mod Loading Screen", + "modmenu.descriptionTranslation.mod-loading-screen": "An advanced loading screen with the loading progress of mods", + + "modloadingscreen.title.loading_quilt": "Loading Quilt Loader", + "modloadingscreen.title.loading_fabric": "Loading Fabric Loader", + "modloadingscreen.title.loading_mod": "Loading %s %s", + + "modloadingscreen.memory.usage": "%d MB / %d MB", + + "modloadingscreen.entrypoint.status": "Loading '%s' (%s) \u2014 %d/%d", + "modloadingscreen.entrypoint.status_mod": "Loading '%s' (%s) \u2014 %d/%d \u2014 %s", + + "modloadingscreen.error.ipc_client": "An error occurred in Mod Loading Screen's IPC client\n%s", + "modloadingscreen.error.ipc_client_title": "Mod Loading Screen IPC Client", + "modloadingscreen.error.fake_entrypoint_unsupported": "Fake entrypoint only supported on PreLaunchEntrypoint", + + "modloadingscreen.log.early_loading": "[ModLoadingScreen] I just want to say... I'm loading *really* **extremely** early.", + "modloadingscreen.log.flatlaf_not_found": "[ModLoadingScreen] [ERROR] flatlaf.jar not found! Aborting.", + "modloadingscreen.log.extracted_flatlaf": "[ModLoadingScreen] Extracted flatlaf.jar", + "modloadingscreen.log.dump_active": "[ModLoadingScreen] Transformed class dumping is active", + "modloadingscreen.log.dump_clear_failed": "[ModLoadingScreen] [ERROR] Failed to clear debug dump dir", + "modloadingscreen.log.transforming": "[ModLoadingScreen] Transforming %s", + "modloadingscreen.log.transformer_failed": "[ModLoadingScreen] [ERROR] Transformer %s for %s failed", + "modloadingscreen.log.dump_failed": "[ModLoadingScreen] [ERROR] Failed to dump class %s", + "modloadingscreen.log.transform_failed": "[ModLoadingScreen] [ERROR] Completely failed to transform %s", + "modloadingscreen.log.old_fabric_fallback": "[ModLoadingScreen] New-style FabricLoaderImpl.invokeEntrypoints not found. Assuming old Fabric.", + "modloadingscreen.log.mod_loading_early": "[ModLoadingScreen] I just want to say... I'm loading *really* early.", + "modloadingscreen.log.dual_install_warn1": "[ModLoadingScreen] [WARN] Mod Loading Screen installed as both a mod and an agent.", + "modloadingscreen.log.dual_install_warn2": "[ModLoadingScreen] [WARN] Please avoid doing this. To avoid issues, the mod has disabled itself.", + "modloadingscreen.log.init_failed": "[ModLoadingScreen] Failed to initialize loading screen. Aborting!" +} \ No newline at end of file diff --git a/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json b/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json new file mode 100644 index 0000000..e16e3cf --- /dev/null +++ b/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json @@ -0,0 +1,32 @@ +{ + "modmenu.nameTranslation.mod-loading-screen": "模組載入畫面", + "modmenu.descriptionTranslation.mod-loading-screen": "帶有模組載入進度的高級載入畫面", + + "modloadingscreen.title.loading_quilt": "正在載入 Quilt Loader", + "modloadingscreen.title.loading_fabric": "正在載入 Fabric Loader", + "modloadingscreen.title.loading_mod": "正在載入 %s %s", + + "modloadingscreen.memory.usage": "%d MB / %d MB", + + "modloadingscreen.entrypoint.status": "正在載入 '%s' (%s) \u2014 %d/%d", + "modloadingscreen.entrypoint.status_mod": "正在載入 '%s' (%s) \u2014 %d/%d \u2014 %s", + + "modloadingscreen.error.ipc_client": "模組載入畫面 的 IPC 客戶端發生錯誤\n%s", + "modloadingscreen.error.ipc_client_title": "模組載入畫面 IPC 客戶端", + "modloadingscreen.error.fake_entrypoint_unsupported": "偽進入點僅支援 PreLaunchEntrypoint", + + "modloadingscreen.log.early_loading": "[模組載入畫面] 我只想說... 我的載入時機真的 *非常* **極度** 早期。", + "modloadingscreen.log.flatlaf_not_found": "[模組載入畫面] [錯誤] 找不到 flatlaf.jar!正在中止。", + "modloadingscreen.log.extracted_flatlaf": "[模組載入畫面] 已解壓縮 flatlaf.jar", + "modloadingscreen.log.dump_active": "[模組載入畫面] 已啟用轉換類別轉儲功能", + "modloadingscreen.log.dump_clear_failed": "[模組載入畫面] [錯誤] 清空除錯轉儲目錄失敗", + "modloadingscreen.log.transforming": "[模組載入畫面] 正在轉換 %s", + "modloadingscreen.log.transformer_failed": "[模組載入畫面] [錯誤] %2$s 的轉換器 %1$s 執行失敗", + "modloadingscreen.log.dump_failed": "[模組載入畫面] [錯誤] 轉儲類別 %s 失敗", + "modloadingscreen.log.transform_failed": "[模組載入畫面] [錯誤] 完全無法轉換 %s", + "modloadingscreen.log.old_fabric_fallback": "[模組載入畫面] 未找到新版 FabricLoaderImpl.invokeEntrypoints,將視為舊版 Fabric。", + "modloadingscreen.log.mod_loading_early": "[模組載入畫面] 我只想說... 我的載入時機真的 *非常* 早期。", + "modloadingscreen.log.dual_install_warn1": "[模組載入畫面] [警告] 模組載入畫面 同時以模組和 Agent 方式安裝。", + "modloadingscreen.log.dual_install_warn2": "[模組載入畫面] [警告] 請避免這樣做。為避免問題,模組已自動停用。", + "modloadingscreen.log.init_failed": "[模組載入畫面] 初始化載入畫面失敗。正在中止!" +} \ No newline at end of file