diff --git a/src/main/java/meteordevelopment/meteorclient/addons/AddonManager.java b/src/main/java/meteordevelopment/meteorclient/addons/AddonManager.java
index 54af1c4d00..2e1a10668e 100644
--- a/src/main/java/meteordevelopment/meteorclient/addons/AddonManager.java
+++ b/src/main/java/meteordevelopment/meteorclient/addons/AddonManager.java
@@ -48,6 +48,7 @@ public String getCommit() {
ModMetadata metadata = FabricLoader.getInstance().getModContainer(MeteorClient.MOD_ID).get().getMetadata();
+ MeteorClient.ADDON.id = metadata.getId();
MeteorClient.ADDON.name = metadata.getName();
MeteorClient.ADDON.authors = new String[metadata.getAuthors().size()];
if (metadata.containsCustomValue(MeteorClient.MOD_ID + ":color")) {
@@ -72,6 +73,7 @@ public String getCommit() {
throw new RuntimeException("Exception during addon init \"%s\".".formatted(metadata.getName()), throwable);
}
+ addon.id = metadata.getId();
addon.name = metadata.getName();
if (metadata.getAuthors().isEmpty()) throw new RuntimeException("Addon \"%s\" requires at least 1 author to be defined in it's fabric.mod.json. See https://fabricmc.net/wiki/documentation:fabric_mod_json_spec".formatted(addon.name));
diff --git a/src/main/java/meteordevelopment/meteorclient/addons/MeteorAddon.java b/src/main/java/meteordevelopment/meteorclient/addons/MeteorAddon.java
index 7ae5918d6f..5c2eb55fff 100644
--- a/src/main/java/meteordevelopment/meteorclient/addons/MeteorAddon.java
+++ b/src/main/java/meteordevelopment/meteorclient/addons/MeteorAddon.java
@@ -7,9 +7,11 @@
import meteordevelopment.meteorclient.utils.render.color.Color;
-import java.io.InputStream;
-
public abstract class MeteorAddon {
+ /** This field is automatically assigned from fabric.mod.json file.
+ * @since 1.21.11 */ // todo replace with exact version when released
+ public String id;
+
/** This field is automatically assigned from fabric.mod.json file. */
public String name;
@@ -36,26 +38,4 @@ public GithubRepo getRepo() {
public String getCommit() {
return null;
}
-
- /**
- * Example implementation:
- *
{@code
- * @Override
- * public InputStream provideLanguage(String lang) {
- * return Addon.class.getResourceAsStream("/assets/addon-name/language/" + lang + ".json")
- * }
- * }
- *
- *
- * Addons should not store their language files in the /assets/xxx/lang/ path as it opens up users to detection
- * by servers via the translation exploit.
- * Storing them anywhere else should prevent them from getting picked up via the vanilla resource loader.
- *
- * @param lang A language code in lowercase
- * @return An InputStream for the relevant json translation file, or null if the addon doesn't have
- * a file for that language.
- */
- public InputStream provideLanguage(String lang) {
- return null;
- }
}
diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java
index ca08d56e89..7f63c4cd26 100644
--- a/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java
+++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/CommandsCommand.java
@@ -53,7 +53,7 @@ private MutableText getCommandText(Command command) {
}
tooltip.append(aliases.formatted(Formatting.GRAY)).append("\n\n");
- tooltip.append(translatable("description")).formatted(Formatting.WHITE);
+ tooltip.append(command.translatable("description")).formatted(Formatting.WHITE);
// Text
MutableText text = Text.literal(Utils.nameToTitle(command.getName()));
diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/LanguageManagerMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/LanguageManagerMixin.java
index 228143cef8..4a1868909c 100644
--- a/src/main/java/meteordevelopment/meteorclient/mixin/LanguageManagerMixin.java
+++ b/src/main/java/meteordevelopment/meteorclient/mixin/LanguageManagerMixin.java
@@ -17,5 +17,6 @@ public class LanguageManagerMixin {
@Inject(method = "setLanguage", at = @At("TAIL"))
private void onSetLanguage(String languageCode, CallbackInfo ci) {
MeteorTranslations.loadLanguage(languageCode);
+ MeteorTranslations.clearUnusedLanguages(languageCode);
}
}
diff --git a/src/main/java/meteordevelopment/meteorclient/utils/misc/MeteorTranslations.java b/src/main/java/meteordevelopment/meteorclient/utils/misc/MeteorTranslations.java
index 3b449ac3c7..c726bb5d4e 100644
--- a/src/main/java/meteordevelopment/meteorclient/utils/misc/MeteorTranslations.java
+++ b/src/main/java/meteordevelopment/meteorclient/utils/misc/MeteorTranslations.java
@@ -7,11 +7,12 @@
import com.google.gson.Gson;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import meteordevelopment.meteorclient.MeteorClient;
import meteordevelopment.meteorclient.addons.AddonManager;
import meteordevelopment.meteorclient.addons.MeteorAddon;
import meteordevelopment.meteorclient.utils.PreInit;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.resource.language.LanguageDefinition;
import net.minecraft.client.resource.language.ReorderingUtil;
import net.minecraft.text.OrderedText;
import net.minecraft.text.StringVisitable;
@@ -20,67 +21,79 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.IllegalFormatException;
-import java.util.List;
-import java.util.Map;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
import static meteordevelopment.meteorclient.MeteorClient.mc;
@SuppressWarnings("unused")
public class MeteorTranslations {
+ private static final String EN_US_CODE = "en_us";
private static final Gson GSON = new Gson();
private static final Map languages = new Object2ObjectOpenHashMap<>();
+ private static MeteorLanguage defaultLanguage;
@PreInit
public static void preInit() {
List toLoad = new ArrayList<>(2);
- toLoad.add("en_us");
- if (!mc.options.language.equalsIgnoreCase("en_us")) toLoad.add(mc.options.language);
+ toLoad.add(EN_US_CODE);
+ if (!mc.options.language.equals(EN_US_CODE)) toLoad.add(mc.options.language);
for (String language : toLoad) {
loadLanguage(language);
}
+
+ defaultLanguage = getLanguage(EN_US_CODE);
}
public static void loadLanguage(String languageCode) {
- languageCode = languageCode.toLowerCase();
if (languages.containsKey(languageCode)) return;
+ LanguageDefinition definition = MinecraftClient.getInstance().getLanguageManager().getLanguage(languageCode);
+ if (definition == null) return;
+
+ Object2ObjectOpenHashMap languageMap = new Object2ObjectOpenHashMap<>();
+
try (InputStream stream = MeteorTranslations.class.getResourceAsStream("/assets/meteor-client/language/" + languageCode + ".json")) {
if (stream == null) {
- if (languageCode.equals("en_us")) throw new RuntimeException("Error loading the default language");
+ if (languageCode.equals(EN_US_CODE)) throw new RuntimeException("Error loading the default language");
else MeteorClient.LOG.info("No language file found for '{}'", languageCode);
}
else {
// noinspection unchecked
- Object2ObjectOpenHashMap map = GSON.fromJson(new InputStreamReader(stream), Object2ObjectOpenHashMap.class);
- languages.put(languageCode, new MeteorLanguage(map));
+ Object2ObjectOpenHashMap map = GSON.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), Object2ObjectOpenHashMap.class);
+ languageMap.putAll(map);
MeteorClient.LOG.info("Loaded language: {}", languageCode);
}
} catch (IOException e) {
- if (languageCode.equals("en_us")) throw new RuntimeException(e);
+ if (languageCode.equals(EN_US_CODE)) throw new RuntimeException("Error loading default language", e);
else MeteorClient.LOG.error("Error loading language: {}", languageCode, e);
}
for (MeteorAddon addon : AddonManager.ADDONS) {
if (addon == MeteorClient.ADDON) continue;
- try (InputStream stream = addon.provideLanguage(languageCode)) {
+ try (InputStream stream = addon.getClass().getResourceAsStream("/assets/" + addon.id + "/language/" + languageCode + ".json")) {
if (stream == null) continue;
- MeteorLanguage lang = languages.getOrDefault(languageCode, new MeteorLanguage());
// noinspection unchecked
- Object2ObjectOpenHashMap map = GSON.fromJson(new InputStreamReader(stream), Object2ObjectOpenHashMap.class);
- lang.addCustomTranslation(map);
- languages.put(languageCode, lang);
+ Object2ObjectOpenHashMap map = GSON.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), Object2ObjectOpenHashMap.class);
+ languageMap.putAll(map);
MeteorClient.LOG.info("Loaded language {} from addon {}", languageCode, addon.name);
} catch (IOException e) {
MeteorClient.LOG.error("Error loading language {} from addon {}", languageCode, addon.name, e);
}
}
+
+ if (!languageMap.isEmpty()) {
+ languages.put(languageCode, new MeteorLanguage(definition.rightToLeft(), languageMap));
+ }
+ }
+
+ public static void clearUnusedLanguages(String currentLanguageCode) {
+ languages.keySet().removeIf(languageCode -> !languageCode.equals(EN_US_CODE) && !languageCode.equals(currentLanguageCode));
}
public static String translate(String key, Object... args) {
@@ -110,11 +123,11 @@ public static MeteorLanguage getLanguage(String lang) {
}
public static MeteorLanguage getCurrentLanguage() {
- return languages.getOrDefault(mc.options.language.toLowerCase(), getDefaultLanguage());
+ return languages.getOrDefault(mc.options.language, getDefaultLanguage());
}
public static MeteorLanguage getDefaultLanguage() {
- return languages.get("en_us");
+ return defaultLanguage;
}
/**
@@ -125,60 +138,42 @@ public static double percentLocalised() {
// translation. Maybe that will change in the future.
if (isEnglish()) return 100;
- double currentLangSize = languages.getOrDefault(mc.options.language.toLowerCase(), new MeteorLanguage()).translations.size();
+ MeteorLanguage currentLang = languages.get(mc.options.language);
+ double currentLangSize = currentLang != null ? currentLang.translations.size() : 0;
return (currentLangSize / getDefaultLanguage().translations.size()) * 100;
}
public static boolean isEnglish() {
- return mc.options.language.toLowerCase().startsWith("en");
+ return mc.options.language.startsWith("en");
}
public static class MeteorLanguage extends Language {
private final Map translations = new Object2ObjectOpenHashMap<>();
- private final List