feat: add I18n support
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
|
||||||
|
Date: Thu, 12 Jun 2025 04:44:24 +0800
|
||||||
|
Subject: [PATCH] I18n support
|
||||||
|
|
||||||
|
|
||||||
|
diff --git a/net/minecraft/locale/Language.java b/net/minecraft/locale/Language.java
|
||||||
|
index 7b9e2a1a208b46a69c16e6afd8b502259893574f..cdd26bbc6f6ff5510347049706e0f6f344fe79b1 100644
|
||||||
|
--- a/net/minecraft/locale/Language.java
|
||||||
|
+++ b/net/minecraft/locale/Language.java
|
||||||
|
@@ -31,11 +31,27 @@ public abstract class Language {
|
||||||
|
public static final String DEFAULT = "en_us";
|
||||||
|
private static volatile Language instance = loadDefault();
|
||||||
|
|
||||||
|
+ // Lophine start - I18n support
|
||||||
|
private static Language loadDefault() {
|
||||||
|
+ return load("/assets/minecraft/lang/" + DEFAULT + ".json");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public static void loadLocalLang(String lang) {
|
||||||
|
+ try {
|
||||||
|
+ inject(load(lang));
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ LOGGER.error("Failed to load language file for {}", me.earthme.lophine.config.modules.optimizations.LanguageConfig.lang, e);
|
||||||
|
+ inject(loadDefault());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Lophine end - I18n support
|
||||||
|
+
|
||||||
|
+ private static Language load(String lang) { // Lophine - I18n support
|
||||||
|
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
BiConsumer<String, String> biConsumer = map::put;
|
||||||
|
- parseTranslations(biConsumer, "/assets/minecraft/lang/en_us.json");
|
||||||
|
+ parseTranslations(biConsumer, lang); // Lophine - I18n support
|
||||||
|
deprecatedTranslationsInfo.applyToMap(map);
|
||||||
|
final Map<String, String> map1 = Map.copyOf(map);
|
||||||
|
return new Language() {
|
||||||
|
@@ -65,13 +81,26 @@ public abstract class Language {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // Lophine start - I18n support
|
||||||
|
private static void parseTranslations(BiConsumer<String, String> output, String languagePath) {
|
||||||
|
try (InputStream resourceAsStream = Language.class.getResourceAsStream(languagePath)) {
|
||||||
|
loadFromJson(resourceAsStream, output);
|
||||||
|
- } catch (JsonParseException | IOException var7) {
|
||||||
|
- LOGGER.error("Couldn't read strings from {}", languagePath, var7);
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ try {
|
||||||
|
+ java.nio.file.Path filePath = java.nio.file.Paths.get(languagePath);
|
||||||
|
+ try (InputStream fileStream = java.nio.file.Files.newInputStream(filePath)) {
|
||||||
|
+ loadFromJson(fileStream, output);
|
||||||
|
+ } catch (java.nio.file.NoSuchFileException fileEx) {
|
||||||
|
+ LOGGER.error("Language file not found in both locations: {}", languagePath);
|
||||||
|
+ } catch (Exception fileEx) {
|
||||||
|
+ LOGGER.error("Failed to load from filesystem {}", filePath, fileEx);
|
||||||
|
+ }
|
||||||
|
+ } catch (Exception ep) {
|
||||||
|
+ LOGGER.error("Couldn't read strings from {}", languagePath, ep);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ // Lophine end - I18n support
|
||||||
|
|
||||||
|
public static void loadFromJson(InputStream stream, BiConsumer<String, String> output) {
|
||||||
|
JsonObject jsonObject = GSON.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), JsonObject.class);
|
||||||
|
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
index ba0cdba050cd3bf9231e15dd9543d34f37d9dae5..ce7d70afa524e5b8ea6f9de75d34c8f1eaa6ad72 100644
|
||||||
|
--- a/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
|
||||||
|
@@ -172,6 +172,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
||||||
|
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
|
||||||
|
// Paper end - initialize global and world-defaults configuration
|
||||||
|
me.earthme.lophine.config.LophineConfig.loadConfigFiles(); //Luminol - load config file // Lophine - load config file
|
||||||
|
+ me.earthme.lophine.utils.LocalLangUtil.init(); // Lophine - I18n support
|
||||||
|
if (false) this.server.spark.enableEarlyIfRequested(); // Paper - spark // Luminol - Force disable builtin spark
|
||||||
|
// Paper start - fix converting txt to json file; convert old users earlier after PlayerList creation but before file load/save
|
||||||
|
if (this.convertOldUsers()) {
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
--- /dev/null
|
||||||
|
+++ b/src/main/java/me/earthme/lophine/config/modules/optimizations/LanguageConfig.java
|
||||||
|
@@ -1,0 +_,22 @@
|
||||||
|
+package me.earthme.lophine.config.modules.optimizations;
|
||||||
|
+
|
||||||
|
+import me.earthme.luminol.config.EnumConfigCategory;
|
||||||
|
+import me.earthme.luminol.config.IConfigModule;
|
||||||
|
+import me.earthme.luminol.config.flags.ConfigInfo;
|
||||||
|
+
|
||||||
|
+public class LanguageConfig implements IConfigModule {
|
||||||
|
+ @ConfigInfo(baseName = "lang" ,comments = """
|
||||||
|
+ Please use the key from https://minecraft.wiki/w/Language
|
||||||
|
+ Sample of format: en_us zh_cn zh_hk zh_tw""")
|
||||||
|
+ public static String lang = "en_us";
|
||||||
|
+
|
||||||
|
+ @Override
|
||||||
|
+ public EnumConfigCategory getCategory() {
|
||||||
|
+ return EnumConfigCategory.OPTIMIZATIONS;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Override
|
||||||
|
+ public String getBaseName() {
|
||||||
|
+ return "language";
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
--- /dev/null
|
||||||
|
+++ b/src/main/java/me/earthme/lophine/utils/LocalLangUtil.java
|
||||||
|
@@ -1,0 +_,156 @@
|
||||||
|
+package me.earthme.lophine.utils;
|
||||||
|
+
|
||||||
|
+import com.google.gson.JsonElement;
|
||||||
|
+import com.google.gson.JsonObject;
|
||||||
|
+import com.google.gson.JsonParser;
|
||||||
|
+
|
||||||
|
+import java.io.ByteArrayInputStream;
|
||||||
|
+import java.io.IOException;
|
||||||
|
+import java.io.InputStream;
|
||||||
|
+import java.io.InputStreamReader;
|
||||||
|
+import java.net.HttpURLConnection;
|
||||||
|
+import java.net.URL;
|
||||||
|
+import java.nio.file.Files;
|
||||||
|
+import java.nio.file.Path;
|
||||||
|
+import java.nio.file.Paths;
|
||||||
|
+import java.util.Objects;
|
||||||
|
+import java.util.concurrent.CompletableFuture;
|
||||||
|
+import java.util.logging.Logger;
|
||||||
|
+
|
||||||
|
+import static me.earthme.lophine.config.modules.optimizations.LanguageConfig.lang;
|
||||||
|
+
|
||||||
|
+public class LocalLangUtil {
|
||||||
|
+ static Logger logger = Logger.getLogger("LangLoader");
|
||||||
|
+ static String VERSION = "1.21.5";
|
||||||
|
+ static String basePath = "cache/lophine/" + VERSION + "/";
|
||||||
|
+
|
||||||
|
+ public static void init() {
|
||||||
|
+ if (Objects.equals(lang, "en_us")) return;
|
||||||
|
+ CompletableFuture.runAsync(() -> {
|
||||||
|
+ try {
|
||||||
|
+ downloadLangAndCheck();
|
||||||
|
+ net.minecraft.locale.Language.loadLocalLang(basePath + "lang/" + lang + ".json");
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ logger.severe(() -> "Async initialization failed: " + e.getMessage());
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private static void downloadLangAndCheck() {
|
||||||
|
+ Path path = Paths.get(basePath + VERSION + ".json");
|
||||||
|
+ JsonObject json;
|
||||||
|
+ if (Files.exists(path)) {
|
||||||
|
+ try {
|
||||||
|
+ json = loadJson(path.toString());
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ logger.warning("Failed to load local JSON: " + e.getMessage());
|
||||||
|
+ json = download();
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ json = download();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (json == null) {
|
||||||
|
+ logger.warning("Failed to load language metadata");
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ try {
|
||||||
|
+ JsonObject assetIndex = json.getAsJsonObject("assetIndex");
|
||||||
|
+ String assetUrl = assetIndex.get("url").getAsString();
|
||||||
|
+ byte[] assetData = fetchAndSave(assetUrl, basePath + "resource.json");
|
||||||
|
+
|
||||||
|
+ JsonObject assets = JsonParser.parseString(new String(assetData)).getAsJsonObject();
|
||||||
|
+ JsonObject langEntry = assets.getAsJsonObject("objects")
|
||||||
|
+ .getAsJsonObject("minecraft/lang/" + lang + ".json");
|
||||||
|
+
|
||||||
|
+ String hash = langEntry.get("hash").getAsString();
|
||||||
|
+ if (hash == null || hash.length() < 2) {
|
||||||
|
+ throw new IllegalArgumentException("Invalid hash value");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ downloadLang(hash);
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ logger.warning("Asset processing failed: " + e.getMessage());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private static void downloadLang(String hash) {
|
||||||
|
+ String url = "https://resources.download.minecraft.net/"
|
||||||
|
+ + hash.substring(0, 2) + "/" + hash;
|
||||||
|
+
|
||||||
|
+ for (int i = 0; i < 3; i++) {
|
||||||
|
+ try {
|
||||||
|
+ fetchAndSave(url, basePath + "lang/" + lang + ".json");
|
||||||
|
+ return;
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ if (i == 2) logger.warning("Final attempt failed: " + e.getMessage());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ private static JsonObject download() {
|
||||||
|
+ String versionManifestUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||||
|
+ String targetVersionUrl = null;
|
||||||
|
+
|
||||||
|
+ // Phase 1: Fetch version manifest
|
||||||
|
+ for (int i = 0; i < 3; i++) {
|
||||||
|
+ try (InputStreamReader reader = new InputStreamReader(
|
||||||
|
+ new ByteArrayInputStream(fetch(versionManifestUrl)))) {
|
||||||
|
+ JsonObject manifest = JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
|
+ for (JsonElement element : manifest.getAsJsonArray("versions")) {
|
||||||
|
+ JsonObject version = element.getAsJsonObject();
|
||||||
|
+ if (VERSION.equals(version.get("id").getAsString())) {
|
||||||
|
+ targetVersionUrl = version.get("url").getAsString();
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (targetVersionUrl != null) break;
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ logger.warning("Failed to fetch version manifest: " + e.getMessage());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (targetVersionUrl == null) return null;
|
||||||
|
+
|
||||||
|
+ // Phase 2: Fetch version metadata
|
||||||
|
+ for (int i = 0; i < 3; i++) {
|
||||||
|
+ try (InputStreamReader reader = new InputStreamReader(
|
||||||
|
+ new ByteArrayInputStream(fetchAndSave(targetVersionUrl, basePath + VERSION + ".json")))) {
|
||||||
|
+ return JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ logger.warning("Failed to fetch version metadata: " + e.getMessage());
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return null;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public static byte[] fetch(String urlString) throws IOException {
|
||||||
|
+ HttpURLConnection conn = (HttpURLConnection) new URL(urlString).openConnection();
|
||||||
|
+ conn.setRequestMethod("GET");
|
||||||
|
+ conn.setConnectTimeout(5000);
|
||||||
|
+ conn.setReadTimeout(10000);
|
||||||
|
+
|
||||||
|
+ try (InputStream stream = conn.getInputStream()) {
|
||||||
|
+ if (conn.getResponseCode() != 200) {
|
||||||
|
+ throw new IOException("HTTP error: " + conn.getResponseCode());
|
||||||
|
+ }
|
||||||
|
+ return stream.readAllBytes();
|
||||||
|
+ } finally {
|
||||||
|
+ conn.disconnect();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public static byte[] fetchAndSave(String url, String savePath) throws IOException {
|
||||||
|
+ byte[] data = fetch(url);
|
||||||
|
+ Path outputPath = Paths.get(savePath);
|
||||||
|
+ Files.createDirectories(outputPath.getParent());
|
||||||
|
+ Files.write(outputPath, data);
|
||||||
|
+ return data;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public static JsonObject loadJson(String path) throws IOException {
|
||||||
|
+ byte[] data = Files.readAllBytes(Paths.get(path));
|
||||||
|
+ return JsonParser.parseString(new String(data)).getAsJsonObject();
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
Reference in New Issue
Block a user