diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index f16f0cb..9b42691 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -5,16 +5,18 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v2.1.0
+ uses: actions/setup-java@v4
with:
java-version: 17
distribution: zulu
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Upload build JARs
- uses: actions/upload-artifact@v2.3.1
+ uses: actions/upload-artifact@v4
with:
name: build-jars
path: build/libs/*
diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java b/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java
index 580f0e4..8a8f6e0 100644
--- a/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java
+++ b/src/api/java/io/github/gaming32/modloadingscreen/api/AvailableFeatures.java
@@ -3,58 +3,16 @@
import java.util.StringJoiner;
public final class AvailableFeatures {
- /**
- * @since 1.0.3
- * @see LoadingScreenApi#getFinalEntrypoints
- */
@SuppressWarnings("PointlessBitwiseExpression")
public static final long FINAL_ENTRYPOINTS = 1L << 0;
-
- /**
- * @since 1.0.3
- * @see LoadingScreenApi#isHeadless
- */
public static final long HEADLESS_CHECK = 1L << 1;
-
- /**
- * @since 1.0.3
- * @see LoadingScreenApi#isUsingIpc
- */
@Deprecated
public static final long IPC_CHECK = 1L << 2;
-
- /**
- * @since 1.0.3
- * @see LoadingScreenApi#getActiveProgressBars
- * @see LoadingScreenApi#getProgress
- */
public static final long GET_PROGRESS = 1L << 3;
-
- /**
- * @since 1.0.3
- * @see LoadingScreenApi#isOpen
- */
public static final long OPEN_CHECK = 1L << 4;
-
- /**
- * @since 1.0.4
- * @see LoadingScreenApi#getCustomProgressBar
- * @see CustomProgressBar
- */
public static final long CUSTOM_PROGRESS_BARS = 1L << 5;
- /**
- * All the features that should be available on version 1.0.3.
- *
- * @since 1.0.3
- */
public static final long V1_0_3 = FINAL_ENTRYPOINTS | HEADLESS_CHECK | IPC_CHECK | GET_PROGRESS | OPEN_CHECK;
-
- /**
- * All the features that should be available on version 1.0.4.
- *
- * @since 1.0.4
- */
public static final long V1_0_4 = V1_0_3 | CUSTOM_PROGRESS_BARS;
private static final long MIN_FEATURE = FINAL_ENTRYPOINTS;
@@ -63,37 +21,39 @@ public final class AvailableFeatures {
private AvailableFeatures() {
}
+ public static String getTranslationKey(long feature) {
+ switch ((int) feature) {
+ case (int) FINAL_ENTRYPOINTS:
+ return "modloadingscreen.feature.final_entrypoints";
+ case (int) HEADLESS_CHECK:
+ return "modloadingscreen.feature.headless_check";
+ case (int) IPC_CHECK:
+ return "modloadingscreen.feature.ipc_check";
+ case (int) GET_PROGRESS:
+ return "modloadingscreen.feature.get_progress";
+ case (int) OPEN_CHECK:
+ return "modloadingscreen.feature.open_check";
+ case (int) CUSTOM_PROGRESS_BARS:
+ return "modloadingscreen.feature.custom_progress_bars";
+ default:
+ return "modloadingscreen.feature.unknown";
+ }
+ }
+
public static String toString(long features) {
if (Long.bitCount(features) <= 1L) {
- switch ((int)features) {
- case (int)FINAL_ENTRYPOINTS:
- return "FINAL_ENTRYPOINTS";
- case (int)HEADLESS_CHECK:
- return "HEADLESS_CHECK";
- case (int)IPC_CHECK:
- return "IPC_CHECK";
- case (int)GET_PROGRESS:
- return "GET_PROGRESS";
- case (int)OPEN_CHECK:
- return "OPEN_CHECK";
- case (int)CUSTOM_PROGRESS_BARS:
- return "CUSTOM_PROGRESS_BARS";
- }
- return "";
+ return getTranslationKey(features);
}
final StringJoiner sj = new StringJoiner(", ");
for (long feature = MIN_FEATURE; feature <= MAX_FEATURE; feature <<= 1) {
if (hasFeatures(features, feature)) {
- sj.add(toString(feature));
+ sj.add(getTranslationKey(feature));
}
}
return sj.toString();
}
- /**
- * @see LoadingScreenApi#hasFeatures
- */
public static boolean hasFeatures(long supportedFeatures, long requestedFeatures) {
return (supportedFeatures & requestedFeatures) != 0;
}
diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java b/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java
index 7438595..60fad5c 100644
--- a/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java
+++ b/src/api/java/io/github/gaming32/modloadingscreen/api/CustomProgressBar.java
@@ -3,26 +3,23 @@
import java.io.Closeable;
import java.util.Objects;
-/**
- * A reference to a custom progress bar. Recommended to be used with try-with-resources.
- *
- * @see LoadingScreenApi#getCustomProgressBar
- */
public final class CustomProgressBar implements Closeable {
private final String id;
private final boolean isReal;
private boolean closed;
- private String title;
+ private String titleKey;
+ private String[] titleArgs;
private int progress;
private int minimum;
private int maximum;
private boolean indeterminate;
- CustomProgressBar(String id, boolean isReal, String title, int maximum) {
+ CustomProgressBar(String id, boolean isReal, String titleKey, int maximum, String... titleArgs) {
this.id = id;
this.isReal = isReal;
- this.title = title;
+ this.titleKey = titleKey;
+ this.titleArgs = titleArgs != null ? titleArgs : new String[0];
this.maximum = maximum;
}
@@ -40,9 +37,6 @@ private void checkClosed() {
}
}
- /**
- * Close the progress bar and remove it from the loading screen.
- */
@Override
public void close() {
if (LoadingScreenApi.CUSTOM_PROGRESS_BARS.remove(id) == this) {
@@ -50,10 +44,6 @@ public void close() {
}
}
- /**
- * Sets the progress of the progress bar. The new progress will be clamped to the {@code [minimum, maximum]} range.
- * @param progress The new progress.
- */
public void setProgress(int progress) {
progress = Math.min(maximum, Math.max(minimum, progress));
checkClosed();
@@ -62,36 +52,19 @@ public void setProgress(int progress) {
LoadingScreenApi.customProgressBarOp(id, "progress", Integer.toString(progress));
}
- /**
- * Gets the current progress of the progress bar.
- */
public int getProgress() {
return progress;
}
- /**
- * Steps the progress bar forward one step.
- *
- * @return The new progress of the progress bar, which may be clamped into range.
- */
public int step() {
return step(1);
}
- /**
- * Steps the progress bar forward {@code n} steps.
- *
- * @return The new progress of the progress bar, which may be clamped into range.
- */
public int step(int n) {
setProgress(progress + n);
return progress;
}
- /**
- * Sets the maximum value of the progress bar.
- * @throws IllegalArgumentException If {@code maximum < minimum}
- */
public void setMaximum(int maximum) {
if (maximum < minimum) {
throw new IllegalArgumentException("maximum may not be less than minimum");
@@ -102,18 +75,10 @@ public void setMaximum(int maximum) {
LoadingScreenApi.customProgressBarOp(id, "maximum", Integer.toString(maximum));
}
- /**
- * Gets the maximum value of the progress bar.
- */
public int getMaximum() {
return maximum;
}
-
- /**
- * Sets the minimum value of the progress bar.
- * @throws IllegalArgumentException If {@code minimum > maximum}
- */
public void setMinimum(int minimum) {
if (minimum > maximum) {
throw new IllegalArgumentException("minimum may not be greater than maximum");
@@ -124,36 +89,31 @@ public void setMinimum(int minimum) {
LoadingScreenApi.customProgressBarOp(id, "minimum", Integer.toString(minimum));
}
-
- /**
- * Gets the minimum value of the progress bar.
- */
public int getMinimum() {
return minimum;
}
- /**
- * Sets the display text of the progress bar.
- */
- public void setTitle(String title) {
- Objects.requireNonNull(title, "title");
+ public void setTitle(String titleKey, String... titleArgs) {
+ Objects.requireNonNull(titleKey, "titleKey");
checkClosed();
- if (title.equals(this.title)) return;
- this.title = title;
- LoadingScreenApi.customProgressBarOp(id, "title", title);
+ this.titleKey = titleKey;
+ this.titleArgs = titleArgs != null ? titleArgs : new String[0];
+ String[] opArgs = new String[this.titleArgs.length + 3];
+ opArgs[0] = id;
+ opArgs[1] = "title";
+ opArgs[2] = titleKey;
+ System.arraycopy(this.titleArgs, 0, opArgs, 3, this.titleArgs.length);
+ LoadingScreenApi.customProgressBarOp(opArgs);
+ }
+
+ public String getTitleKey() {
+ return titleKey;
}
- /**
- * Gets the display text of the progress bar.
- */
- public String getTitle() {
- return title;
+ public String[] getTitleArgs() {
+ return titleArgs.clone();
}
- /**
- * Sets whether the progress bar is indeterminate. If the progress bar is indeterminate, {@link #setProgress} will
- * still take effect, but it won't be visible until the progress bar is determinate.
- */
public void setIndeterminate(boolean indeterminate) {
checkClosed();
if (indeterminate == this.indeterminate) return;
@@ -161,17 +121,10 @@ public void setIndeterminate(boolean indeterminate) {
LoadingScreenApi.customProgressBarOp(id, "indeterminate", Boolean.toString(indeterminate));
}
- /**
- * Returns whether the progress bar is currently indeterminate.
- */
public boolean isIndeterminate() {
return indeterminate;
}
- /**
- * Returns whether the progress bar is a real progress bar that can show up. This returns {@code true} if a version
- * of Mod Loading Screen that supports custom progress bars is installed.
- */
public boolean isReal() {
return isReal;
}
diff --git a/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java b/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java
index b82a001..2a3914a 100644
--- a/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java
+++ b/src/api/java/io/github/gaming32/modloadingscreen/api/LoadingScreenApi.java
@@ -85,7 +85,7 @@ public final class LoadingScreenApi {
try {
createCustomProgressBar = lookup.findStatic(
alsClass, "createCustomProgressBar",
- MethodType.methodType(void.class, String.class, String.class, int.class)
+ MethodType.methodType(void.class, String.class, String.class, int.class, String[].class)
);
customProgressBarOp = lookup.findStatic(
alsClass, "customProgressBarOp",
@@ -93,8 +93,20 @@ public final class LoadingScreenApi {
);
features |= AvailableFeatures.CUSTOM_PROGRESS_BARS;
} catch (Exception e) {
- createCustomProgressBar = null;
- loadFailed(">=1.0.4", AvailableFeatures.CUSTOM_PROGRESS_BARS, e);
+ try {
+ createCustomProgressBar = lookup.findStatic(
+ alsClass, "createCustomProgressBar",
+ MethodType.methodType(void.class, String.class, String.class, int.class)
+ );
+ customProgressBarOp = lookup.findStatic(
+ alsClass, "customProgressBarOp",
+ MethodType.methodType(void.class, String[].class)
+ );
+ features |= AvailableFeatures.CUSTOM_PROGRESS_BARS;
+ } catch (Exception ex) {
+ createCustomProgressBar = null;
+ loadFailed(">=1.0.4", AvailableFeatures.CUSTOM_PROGRESS_BARS, ex);
+ }
}
System.out.println("[ModLoadingScreen] API loaded with features: " + AvailableFeatures.toString(features));
@@ -104,8 +116,6 @@ public final class LoadingScreenApi {
System.err.println(message);
e.printStackTrace();
} else {
- // This API could be called with Mod Loading Screen simply absent, in which case this is *not* an error
- // condition
System.out.println(message);
System.out.println("[ModLoadingScreen] This is not an error, because Mod Loading Screen is not installed.");
}
@@ -159,40 +169,14 @@ private static void loadFailed(String mlsVersionRequired, long feature, Exceptio
private LoadingScreenApi() {
}
- /**
- * Returns the features of the API that are available to use, as a bit mask of flags from
- * {@link AvailableFeatures}. Any features not available will default to no-op fallback implementations.
- *
- * @see AvailableFeatures
- */
public static long getFeatures() {
return FEATURES;
}
- /**
- * Returns whether the bitmask of features is supported.
- *
- * @param requestedFeatures A bitmask of feature flags from {@link AvailableFeatures}
- *
- * @return Whether all the requested features are available
- *
- * @see AvailableFeatures
- * @see AvailableFeatures#hasFeatures
- */
public static boolean hasFeatures(long requestedFeatures) {
return AvailableFeatures.hasFeatures(FEATURES, requestedFeatures);
}
- /**
- * Invokes an entrypoint with a clean API. If Mod Loading Screen is available, its progress will show up in the
- * loading screen.
- *
- * @throws RuntimeException If any entrypoints threw an exception
- *
- * @apiNote This feature is always available, regardless of the return value of {@link #getFeatures}.
- * Calling this without Mod Loading Screen will work always, but just won't show up in the (non-existent) loading
- * screen.
- */
public static void invokeEntrypoint(String name, Class type, Consumer super T> invoker) throws RuntimeException {
if (FABRIC_0_14_23_INVOKE_ENTRYPOINTS != null) {
try {
@@ -205,9 +189,6 @@ public static void invokeEntrypoint(String name, Class type, Consumer s
try {
EntrypointUtils.invoke(name, type, invoker);
} catch (RuntimeException e) {
- // Quilt bug! Quilt's EntrypointExceptions are never converted to Fabric's!
- // https://github.com/QuiltMC/quilt-loader/issues/366
- // Fixed in Quilt since 0.21.0-beta.4. Fix kept for backwards compat.
Class> clazz = e.getClass();
while (clazz != null) {
if (clazz.getName().equals("org.quiltmc.loader.api.entrypoint.EntrypointException")) {
@@ -224,21 +205,6 @@ public static void invokeEntrypoint(String name, Class type, Consumer s
}
}
- /**
- * Returns a set of "final entrypoint" names. "Final entrypoints" are entrypoints that, when finished invoking,
- * will close the loading screen. You can use the return value to add or remove entrypoints so that they don't
- * exit the loading screen, and you can add your own entrypoints that should close the loading screen instead. Be
- * careful about mod compatibility when using this!
- *
- * @apiNote If {@link #getFeatures} doesn't include {@link AvailableFeatures#FINAL_ENTRYPOINTS}, this will return
- * an empty {@link Set} that does nothing when modified, and changes to it will not be seen by other mods.
- *
- * @return The mutable set of "final entrypoints".
- *
- * @see AvailableFeatures#FINAL_ENTRYPOINTS
- *
- * @since 1.0.3
- */
@SuppressWarnings("unchecked")
public static Set getFinalEntrypoints() {
if (FINAL_ENTRYPOINTS == null) {
@@ -251,18 +217,6 @@ public static Set getFinalEntrypoints() {
}
}
- /**
- * Returns whether the Mod Loading Screen (and the game in general) is running in a headless environment. If
- * {@link #getFeatures} doesn't include {@link AvailableFeatures#HEADLESS_CHECK}, this will return the value of
- * {@link GraphicsEnvironment#isHeadless}.
- *
- * @return {@code true} if running in a headless environment.
- *
- * @see GraphicsEnvironment#isHeadless
- * @see AvailableFeatures#HEADLESS_CHECK
- *
- * @since 1.0.3
- */
public static boolean isHeadless() {
if (IS_HEADLESS == null) {
return GraphicsEnvironment.isHeadless();
@@ -274,21 +228,6 @@ public static boolean isHeadless() {
}
}
- /**
- * Returns whether IPC is being used for the loading screen, and hasn't been disabled with
- * {@code mod-loading-screen.disableIpc}. If {@link #isHeadless} returns {@code true}, this will return
- * {@code false}. If {@link #getFeatures} doesn't include {@link AvailableFeatures#IPC_CHECK}, this will return
- * {@code false}.
- *
- * @return {@code true} IPC is being used for the loading screen.
- *
- * @deprecated Non-IPC may be removed in the future, at which point this will always return the opposite of
- * {@link #isHeadless}
- *
- * @see AvailableFeatures#IPC_CHECK
- *
- * @since 1.0.3
- */
@Deprecated
public static boolean isUsingIpc() {
if (ENABLE_IPC == null) {
@@ -313,54 +252,17 @@ private static Map getAllProgress() {
}
}
- /**
- * Returns an {@link Set} of progress bar names. This will be updated dynamically when bars are updated. If
- * {@link #getFeatures} doesn't include {@link AvailableFeatures#GET_PROGRESS}, this will return an empty set.
- *
- * @apiNote Custom progress bars start with {@code "custom:"}, followed by their ID. If {@link #getFeatures}
- * includes {@link AvailableFeatures#CUSTOM_PROGRESS_BARS}, entrypoint progress bars start with
- * {@code "entrypoint:"}
- *
- * @see AvailableFeatures#GET_PROGRESS
- *
- * @since 1.0.3
- */
@UnmodifiableView
public static Set getActiveProgressBars() {
final Set bars = getAllProgress().keySet();
return bars == Collections.EMPTY_SET ? bars : Collections.unmodifiableSet(bars);
}
- /**
- * Returns the current progress of a progress bar, or {@code null} if there is no such progress bar. If
- * {@link #getFeatures} doesn't include {@link AvailableFeatures#GET_PROGRESS}, this will always return
- * {@code null}.
- *
- * @param barName The name of the progress bar. In the case of entrypoints, this is the name of the entrypoint.
- *
- * @apiNote Custom progress bars start with {@code "custom:"}, followed by their ID. If {@link #getFeatures}
- * includes {@link AvailableFeatures#CUSTOM_PROGRESS_BARS}, entrypoint progress bars start with
- * {@code "entrypoint:"}
- *
- * @see AvailableFeatures#GET_PROGRESS
- *
- * @since 1.0.3
- */
@Nullable
public static Integer getProgress(@NotNull String barName) {
return getAllProgress().get(barName);
}
- /**
- * Returns whether a loading screen is currently active. If {@link #getFeatures} doesn't include
- * {@link AvailableFeatures#OPEN_CHECK}, this will always return {@code false}.
- *
- * @return {@code true} if there is a loading screen open.
- *
- * @see AvailableFeatures#OPEN_CHECK
- *
- * @since 1.0.3
- */
public static boolean isOpen() {
if (IS_OPEN == null) {
return false;
@@ -372,35 +274,32 @@ public static boolean isOpen() {
}
}
- /**
- * Creates a custom progress bar.
- * @param title The title of the progress bar. This is the full string to display.
- * @param max The maximum value of the progress bar. It will not be removed automatically when this is
- * reached.
- * @return The {@link CustomProgressBar} reference to the progress bar.
- */
- public static CustomProgressBar getCustomProgressBar(@NotNull String id, @NotNull String title, int max) {
+ public static CustomProgressBar getCustomProgressBar(@NotNull String id, @NotNull String titleKey, int max, String... titleArgs) {
CustomProgressBar bar = CUSTOM_PROGRESS_BARS.get(id);
if (bar == null) {
- CUSTOM_PROGRESS_BARS.put(id, bar = createCustomProgressBar(id, title, max));
+ CUSTOM_PROGRESS_BARS.put(id, bar = createCustomProgressBar(id, titleKey, max, titleArgs));
}
- bar.setTitle(title);
+ bar.setTitle(titleKey, titleArgs);
bar.setMaximum(max);
return bar;
}
- private static CustomProgressBar createCustomProgressBar(String id, String title, int max) {
+ private static CustomProgressBar createCustomProgressBar(String id, String titleKey, int max, String... titleArgs) {
Objects.requireNonNull(id, "id");
- Objects.requireNonNull(title, "title");
+ Objects.requireNonNull(titleKey, "titleKey");
if (CREATE_CUSTOM_PROGRESS_BAR == null) {
- return new CustomProgressBar(id, false, title, max);
+ return new CustomProgressBar(id, false, titleKey, max, titleArgs);
}
try {
- CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, title, max);
+ if (CREATE_CUSTOM_PROGRESS_BAR.type().parameterCount() == 4) {
+ CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, titleKey, max, titleArgs != null ? titleArgs : new String[0]);
+ } else {
+ CREATE_CUSTOM_PROGRESS_BAR.invokeExact(id, titleKey, max);
+ }
} catch (Throwable t) {
rethrow(t);
}
- return new CustomProgressBar(id, true, title, max);
+ return new CustomProgressBar(id, true, titleKey, max, titleArgs);
}
static void customProgressBarOp(String... args) {
diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ActualLoadingScreen.java
index 8c5a56b..e85ce60 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;
@@ -48,7 +56,6 @@ public class ActualLoadingScreen {
!IS_IPC_CLIENT && !IS_HEADLESS && !Boolean.getBoolean("mod-loading-screen.disableIpc");
public static final String[] SUPPORTED_BACKGROUND_FORMATS = {"gif", "jpeg", "png", "xbm"};
- // Unlike progressBars, this is populated on both the IPC client and IPC server, allowing it to be used from the API
public static final Map progress = new LinkedHashMap<>();
private static final Map progressBars = new LinkedHashMap<>();
private static JFrame dialog;
@@ -59,12 +66,67 @@ public class ActualLoadingScreen {
private static Thread memoryThread;
private static boolean titleSet;
- static boolean runningOnQuilt; // Accessed from EarlyLoadingAgent
+ static boolean runningOnQuilt;
+ private static String gameVersion;
private static Path configDir;
private static boolean enableMemoryDisplay = true;
+ private static final Map TRANSLATIONS = new HashMap<>();
+
+ 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);
+ }
+ }
+
+ 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) {
+ }
+ }
+
+ 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 String getLoaderName() {
+ return runningOnQuilt ? translate("modloadingscreen.loader.quilt") : translate("modloadingscreen.loader.fabric");
+ }
+
+ private static String getLoaderIconName() {
+ return runningOnQuilt ? "icon-quilt.png" : "icon-fabric.png";
+ }
+
public static void startLoadingScreen(boolean fabricReady) {
+ loadTranslations();
+
final Path gameDir = fabricReady ? FabricLoader.getInstance().getGameDir() : Paths.get(".").toAbsolutePath();
final Path runDir = gameDir.resolve(".cache/mod-loading-screen");
@@ -89,14 +151,18 @@ public static void startLoadingScreen(boolean fabricReady) {
if (IS_IPC_CLIENT) {
runningOnQuilt = Boolean.getBoolean("mlsipc.quilt");
+ gameVersion = System.getProperty("mlsipc.gameVersion");
configDir = Paths.get(System.getProperty("mlsipc.config"));
} else {
if (fabricReady) {
runningOnQuilt = FabricLoader.getInstance().isModLoaded("quilt_loader");
+ gameVersion = FabricLoader.getInstance().getModContainer("minecraft")
+ .map(c -> c.getMetadata().getVersion().getFriendlyString())
+ .orElse(null);
configDir = FabricLoader.getInstance().getConfigDir().resolve("mod-loading-screen");
} else {
- // This code is certainly something. I don't have any ideas for improvement, though.
- runningOnQuilt = System.getProperty("java.class.path").contains("quilt-loader");
+ final String classpath = System.getProperty("java.class.path");
+ runningOnQuilt = classpath.contains("quilt-loader");
configDir = Paths.get("config/mod-loading-screen").toAbsolutePath();
}
}
@@ -145,6 +211,7 @@ public static void startLoadingScreen(boolean fabricReady) {
System.getProperty("java.home") + "/bin/java",
"-Dmlsipc.present=true",
"-Dmlsipc.quilt=" + runningOnQuilt,
+ "-Dmlsipc.gameVersion=" + (gameVersion != null ? gameVersion : ""),
"-Dmlsipc.config=" + configDir,
"-cp", mlsJarPath + File.pathSeparator + flatlafDestPath,
ACTUAL_LOADING_SCREEN.replace('/', '.')
@@ -173,24 +240,19 @@ public static void startLoadingScreen(boolean fabricReady) {
UIManager.getDefaults().put("ProgressBar.selectionForeground", new Color(255, 255, 255));
dialog = new JFrame();
- if (fabricReady) {
- setFabricTitle();
- } else {
- dialog.setTitle(runningOnQuilt ? "Loading Quilt Loader" : "Loading Fabric Loader");
- }
+ updateTitleWithGameVersion();
dialog.setResizable(false);
try {
- dialog.setIconImage(ImageIO.read(findImageUrl("icon", "icon.png", ImageIO.getReaderFileSuffixes())));
+ dialog.setIconImage(ImageIO.read(findImageUrl("icon", getLoaderIconName(), ImageIO.getReaderFileSuffixes())));
} catch (Exception e) {
- println("Failed to load icon.png", e);
+ println("Failed to load icon", e);
}
ImageIcon background;
try {
- background = new ImageIcon(findImageUrl(
- "background", runningOnQuilt ? "quilt-banner.png" : "aof4.png", SUPPORTED_BACKGROUND_FORMATS
- ));
+ String defaultBanner = runningOnQuilt ? "quilt-banner.png" : "aof4.png";
+ background = new ImageIcon(findImageUrl("background", defaultBanner, SUPPORTED_BACKGROUND_FORMATS));
} catch (Exception e) {
println("Failed to load background.png", e);
background = null;
@@ -249,13 +311,25 @@ private static void loadConfig() {
private static URL findImageUrl(
String prefix, String defaultFilename, String[] formats
) throws MalformedURLException {
+ URL res = ClassLoader.getSystemResource("assets/mod-loading-screen/" + defaultFilename);
+ if (res != null) return res;
+
+ res = ClassLoader.getSystemResource("assets/mod-loading-screen/icon.png");
+ if (res != null) return res;
+
+ final Path specificConfigPath = configDir.resolve(defaultFilename);
+ if (Files.isRegularFile(specificConfigPath)) {
+ return specificConfigPath.toUri().toURL();
+ }
+
for (final String format : formats) {
final Path path = configDir.resolve(prefix + '.' + format);
if (Files.isRegularFile(path)) {
return path.toUri().toURL();
}
}
- return ClassLoader.getSystemResource("assets/mod-loading-screen/" + defaultFilename);
+
+ return null;
}
private static void startMemoryThread() {
@@ -276,19 +350,36 @@ private static void startMemoryThread() {
memoryThread.start();
}
+ public static void setGameVersion(String version) {
+ if (version == null || version.isEmpty()) return;
+ gameVersion = version;
+ sendIpc(7, version);
+ updateTitleWithGameVersion();
+ }
+
+ private static void updateTitleWithGameVersion() {
+ if (gameVersion != null && !gameVersion.isEmpty()) {
+ setTitle(translate("modloadingscreen.title.loading_loader_game", getLoaderName(), gameVersion));
+ } else {
+ setTitle(translate("modloadingscreen.title.loading_loader", getLoaderName()));
+ }
+ }
+
public static void setTitleFromMetadata(String id, String name, String version) {
+ if (id != null && (id.equals("minecraft") || id.equalsIgnoreCase("game"))) {
+ setGameVersion(version);
+ return;
+ }
if (titleSet || IGNORED_BUILTIN.contains(id)) return;
titleSet = true;
- setTitle("Loading " + name + ' ' + version);
+ setTitle(translate("modloadingscreen.title.loading_mod", name, version));
}
private static void setFabricTitle() {
- for (final ModContainer container : FabricLoader.getInstance().getAllMods()) {
- final ModMetadata m = container.getMetadata();
- if (!m.getType().equals("builtin")) continue;
- setTitleFromMetadata(m.getId(), m.getName(), m.getVersion().getFriendlyString());
- if (titleSet) break;
- }
+ FabricLoader.getInstance().getModContainer("minecraft").ifPresent(container -> {
+ setGameVersion(container.getMetadata().getVersion().getFriendlyString());
+ });
+ updateTitleWithGameVersion();
}
private static void setTitle(String title) {
@@ -378,16 +469,21 @@ public static void maybeCloseAfter(String type) {
close();
}
- public static void createCustomProgressBar(String id, String title, int max) {
+ public static void createCustomProgressBar(String id, String titleKey, int max, String... titleArgs) {
final String fullId = "custom:" + id;
progress.put(fullId, 0);
- if (sendIpc(4, id, title, Integer.toString(max))) return;
+ String[] ipcArgs = new String[titleArgs.length + 3];
+ ipcArgs[0] = id;
+ ipcArgs[1] = titleKey;
+ ipcArgs[2] = Integer.toString(max);
+ System.arraycopy(titleArgs, 0, ipcArgs, 3, titleArgs.length);
+ if (sendIpc(4, ipcArgs)) return;
if (dialog == null) return;
final JProgressBar progressBar = new JProgressBar(0, max);
progressBar.setStringPainted(true);
- progressBar.setString(title);
+ progressBar.setString(translate(titleKey, (Object[]) titleArgs));
progressBars.put(fullId, progressBar);
label.add(progressBar, BorderLayout.SOUTH, 1);
label.revalidate();
@@ -427,7 +523,9 @@ public static void customProgressBarOp(String... args) {
progressBar.setMinimum(Integer.parseInt(args[2]));
break;
case "title":
- progressBar.setString(args[2]);
+ String[] formatArgs = new String[args.length - 3];
+ System.arraycopy(args, 3, formatArgs, 0, formatArgs.length);
+ progressBar.setString(translate(args[2], (Object[]) formatArgs));
break;
case "indeterminate":
progressBar.setIndeterminate(Boolean.parseBoolean(args[2]));
@@ -488,17 +586,17 @@ private static void updateMemoryUsage0(long usage, long total) {
memoryBar.setMaximum(totalMb);
memoryBar.setValue(usageMb);
- memoryBar.setString(usageMb + " MB / " + totalMb + " MB");
+ 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();
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) {
@@ -549,7 +647,6 @@ private static boolean sendIpc(int id, String... args) {
return true;
}
- // IPC client
public static void main(String[] args) {
try {
logFile = new PrintStream("ipc-client-log.txt");
@@ -576,7 +673,9 @@ public static void main(String[] args) {
updateMemoryUsage0(Long.parseLong(packetArgs[0]), Long.parseLong(packetArgs[1]));
break;
case 4:
- createCustomProgressBar(packetArgs[0], packetArgs[1], Integer.parseInt(packetArgs[2]));
+ String[] createArgs = new String[packetArgs.length - 3];
+ System.arraycopy(packetArgs, 3, createArgs, 0, createArgs.length);
+ createCustomProgressBar(packetArgs[0], packetArgs[1], Integer.parseInt(packetArgs[2]), createArgs);
break;
case 5:
customProgressBarOp(packetArgs);
@@ -584,6 +683,9 @@ public static void main(String[] args) {
case 6:
setTitle(packetArgs[0]);
break;
+ case 7:
+ setGameVersion(packetArgs[0]);
+ break;
case 255:
break mainLoop;
}
@@ -592,8 +694,8 @@ public static void main(String[] args) {
} catch (Exception e) {
println("Error in IPC client", e);
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();
diff --git a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java
index 06fd58b..e3f8d33 100644
--- a/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java
+++ b/src/main/java/io/github/gaming32/modloadingscreen/EarlyLoadingAgent.java
@@ -11,19 +11,19 @@
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.");
+ 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.");
+ 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");
+ System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.extracted_flatlaf"));
instrumentation.appendToSystemClassLoaderSearch(new JarFile(flatlafDestPath.toFile()));
ActualLoadingScreen.startLoadingScreen(false);
diff --git a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java
index 5f37e3f..4ddfa10 100644
--- a/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java
+++ b/src/main/java/io/github/gaming32/modloadingscreen/MlsTransformers.java
@@ -74,7 +74,7 @@ static byte[] instrumentClass(String name, byte[] bytes) {
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 +93,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 +102,7 @@ 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);
+ 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 +110,7 @@ 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");
+ System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transformer_failed", part, name));
e.printStackTrace();
}
}
@@ -123,14 +123,14 @@ 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);
+ System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.transform_failed", name));
t.printStackTrace();
}
return null;
@@ -142,7 +142,7 @@ 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.");
+ System.out.println(ActualLoadingScreen.translate("modloadingscreen.log.old_fabric_fallback"));
return;
}
final ListIterator it = method.instructions.iterator();
@@ -155,7 +155,7 @@ private static void instrumentFabricLoaderImplInvokeEntrypoints(ClassNode clazz)
if (insn.getOpcode() == Opcodes.ACONST_NULL) break;
}
it.previous();
- mainEntrypointHooks(it, false, true);
+ mainEntrypointHooks(it, false, true);
maybeCloseAfter(it, true);
}
@@ -170,9 +170,6 @@ 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) {
while (it.hasNext()) {
final AbstractInsnNode insn = it.next();
@@ -201,7 +198,6 @@ 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;
@@ -297,32 +293,20 @@ private static void instrumentModDiscovererDiscoverMods(ClassNode clazz, boolean
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) {
@@ -338,31 +322,19 @@ private static void instrumentStandardQuiltPluginAddBuiltinMods(ClassNode clazz)
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
}
}
diff --git a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java
index 68700fd..f0c4c94 100644
--- a/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java
+++ b/src/main/java/io/github/gaming32/modloadingscreen/ModLoadingScreen.java
@@ -27,16 +27,16 @@ 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 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.");
+ 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.");
+ System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn1"));
+ System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.dual_install_warn2"));
return;
}
@@ -87,7 +87,7 @@ public static void init() throws Throwable {
try {
init();
} catch (Throwable t) {
- System.err.println("[ModLoadingScreen] Failed to initialize loading screen. Aborting!");
+ System.err.println(ActualLoadingScreen.translate("modloadingscreen.log.init_failed"));
throw new Error(t);
}
}
diff --git a/src/main/resources/assets/mod-loading-screen/icon-fabric.png b/src/main/resources/assets/mod-loading-screen/icon-fabric.png
new file mode 100644
index 0000000..bb3f0a4
Binary files /dev/null and b/src/main/resources/assets/mod-loading-screen/icon-fabric.png differ
diff --git a/src/main/resources/assets/mod-loading-screen/icon-quilt.png b/src/main/resources/assets/mod-loading-screen/icon-quilt.png
new file mode 100644
index 0000000..9206ed6
Binary files /dev/null and b/src/main/resources/assets/mod-loading-screen/icon-quilt.png differ
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..2f68c26
--- /dev/null
+++ b/src/main/resources/assets/mod-loading-screen/lang/en_us.json
@@ -0,0 +1,45 @@
+{
+ "modmenu.nameTranslation.mod-loading-screen": "Mod Loading Screen",
+ "modmenu.descriptionTranslation.mod-loading-screen": "A fancy loading screen that shows the progress of loading mods",
+
+ "modloadingscreen.loader.fabric": "Fabric",
+ "modloadingscreen.loader.quilt": "Quilt",
+ "modloadingscreen.title.loading_loader": "Loading %s",
+ "modloadingscreen.title.loading_loader_game": "Loading %s %s",
+
+ "modloadingscreen.feature.final_entrypoints": "Final Entrypoints",
+ "modloadingscreen.feature.headless_check": "Headless Check",
+ "modloadingscreen.feature.ipc_check": "IPC Check",
+ "modloadingscreen.feature.get_progress": "Get Progress",
+ "modloadingscreen.feature.open_check": "Open Check",
+ "modloadingscreen.feature.custom_progress_bars": "Custom Progress Bars",
+ "modloadingscreen.feature.unknown": "Unknown Feature",
+
+ "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!"
+}
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..837055f
--- /dev/null
+++ b/src/main/resources/assets/mod-loading-screen/lang/zh_tw.json
@@ -0,0 +1,45 @@
+{
+ "modmenu.nameTranslation.mod-loading-screen": "模組載入畫面",
+ "modmenu.descriptionTranslation.mod-loading-screen": "帶有模組載入進度的高級載入畫面",
+
+ "modloadingscreen.loader.fabric": "Fabric",
+ "modloadingscreen.loader.quilt": "Quilt",
+ "modloadingscreen.title.loading_loader": "正在載入%s",
+ "modloadingscreen.title.loading_loader_game": "正在載入%s %s",
+
+ "modloadingscreen.feature.final_entrypoints": "最終進入點",
+ "modloadingscreen.feature.headless_check": "無介面模式檢查",
+ "modloadingscreen.feature.ipc_check": "IPC 檢查",
+ "modloadingscreen.feature.get_progress": "讀取進度",
+ "modloadingscreen.feature.open_check": "開啟狀態檢查",
+ "modloadingscreen.feature.custom_progress_bars": "自訂進度條",
+ "modloadingscreen.feature.unknown": "未知特性",
+
+ "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": "[模組載入畫面] 初始化載入畫面失敗。正在中止!"
+}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index c706e95..fc501a6 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -7,10 +7,24 @@
"authors": [
"Gaming32"
],
- "contact": {},
+ "contributors": [
+ "StarsShine11904"
+ ],
+ "contact": {
+ "homepage": "https://modrinth.com/mod/mod-loading-screen",
+ "sources": "https://github.com/Gaming32/mod-loading-screen",
+ "issues": "https://github.com/Gaming32/mod-loading-screen/issues"
+ },
"license": "MIT",
"icon": "assets/mod-loading-screen/icon.png",
- "environment": "*",
+ "environment": "client",
+ "custom": {
+ "modmenu": {
+ "badges": [
+ "client"
+ ]
+ }
+ },
"depends": {
"fabricloader": ">=0.12.0"
},