Compare commits

...

3 Commits

Author SHA1 Message Date
Helvetica Volubi cf491363f6 fix: fix some bugs in local lang 2025-06-12 22:21:15 +08:00
Helvetica Volubi 19b773d256 Update upstream (Luminol)
Rewrite I18n module
2025-06-12 12:53:12 +08:00
Helvetica Volubi 700d270b1d feat: add I18n support 2025-06-12 04:50:06 +08:00
9 changed files with 347 additions and 12 deletions
+2 -2
View File
@@ -20,10 +20,10 @@
- 更多有用的功能
## 下载
任何版本都可以在 [Release](https://github.com/LuminolMC/LightingLuminol/releases), 中找到,你也可以通过[以下步骤](./README.md#构建)自己构建。
任何版本都可以在 [Release](https://github.com/LuminolMC/Lophine/releases), 中找到,你也可以通过[以下步骤](./README.md#构建)自己构建。
## 构建
要构建一个paperclip jar,你需要运行以下命令。你可以在lightingluminol-server/build/libs中找到jar(注意:需要`JDK21`
要构建一个paperclip jar,你需要运行以下命令。你可以在lophine-server/build/libs中找到jar(注意:需要`JDK21`
```shell
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
```
+2 -2
View File
@@ -20,10 +20,10 @@
- More functions
## Download
Any versions are available in the [release](https://github.com/LuminolMC/LightingLuminol/releases), also you can build it by yourself through [the following steps](./README_EN.md#Build).
Any versions are available in the [release](https://github.com/LuminolMC/Lophine/releases), also you can build it by yourself through [the following steps](./README_EN.md#Build).
## Build
To build a paperclip jar, you need to run the following command. You can find the jar in build/libs(Note: JDK21 is needed)
To build a paperclip jar, you need to run the following command. You can find the jar in lophine-server/build/libs(Note: `JDK21` is needed)
```shell
./gradlew applyAllPatches && ./gradlew createMojmapPaperclipJar
```
+1 -1
View File
@@ -2,7 +2,7 @@ group=me.earthme.lophine
version=1.21.5-R0.1-SNAPSHOT
mcVersion=1.21.5
luminolRef=6eebbe4221f34bf65832b02af01814fc057206b6
luminolRef=818e1132dfb8a9849b082f6adf767b66c7da8b31
org.gradle.configuration-cache=true
org.gradle.caching=true
@@ -5,16 +5,15 @@ Subject: [PATCH] Rebrand to Lophine
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index fcf599846a38beeb8f094d5376c01d5a690f7a2f..9ddcb2b5ced68ff619516ddbac2327c23e316438 100644
index fcf599846a38beeb8f094d5376c01d5a690f7a2f..a70c04dc72a15e4e8de8de677951f1eb605057b0 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -108,7 +108,8 @@ public class Main {
@@ -108,7 +108,7 @@ public class Main {
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
- me.earthme.luminol.config.LuminolConfig.preLoadConfig(); // Luminol - Luminol config
+ me.earthme.lophine.config.LophineConfig.initConfigs(); // Lophine - Lophine config
+ me.earthme.lophine.config.LophineConfig.preLoad(); // Luminol - Luminol config // Lophine - Lophine config
io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();
@@ -18,7 +18,7 @@ index ee53f78396f4377d3e5c2998826f231208066ef6..607091ecbd4a7261f2c0d839ee4dd1d2
double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1);
if (rangeY != -1) {
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 0814ad6820b08a856f7bd261e96143568e772ac4..b7ef7df56e94b6af9398d0a4768747a35ac6f4fd 100644
index a023cc399fc8bfee7771e5ca6716578f89b7072f..f487b44174678196e2bade0066511daf58e93876 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -140,7 +140,7 @@ import net.minecraft.world.scores.ScoreHolder;
@@ -30,7 +30,7 @@ index 0814ad6820b08a856f7bd261e96143568e772ac4..b7ef7df56e94b6af9398d0a4768747a3
// CraftBukkit start
private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger();
private static final int CURRENT_LEVEL = 2;
@@ -6107,4 +6107,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -6106,4 +6106,46 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// Paper end - Expose entity id counter
public boolean shouldTickHot() { return this.tickCount > 20 * 10 && this.isAlive(); } // KioCG
@@ -0,0 +1,40 @@
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..e2a42996bb01c99fc71a5994e8eeb66ce836af34 100644
--- a/net/minecraft/locale/Language.java
+++ b/net/minecraft/locale/Language.java
@@ -31,7 +31,7 @@ public abstract class Language {
public static final String DEFAULT = "en_us";
private static volatile Language instance = loadDefault();
- private static Language loadDefault() {
+ public static Language loadDefault() { // Lophine - I18n support
DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
Map<String, String> map = new HashMap<>();
BiConsumer<String, String> biConsumer = map::put;
@@ -65,7 +65,7 @@ public abstract class Language {
};
}
- private static void parseTranslations(BiConsumer<String, String> output, String languagePath) {
+ public static void parseTranslations(BiConsumer<String, String> output, String languagePath) { // Lophine - I18n support
try (InputStream resourceAsStream = Language.class.getResourceAsStream(languagePath)) {
loadFromJson(resourceAsStream, output);
} catch (JsonParseException | IOException var7) {
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()) {
@@ -1,6 +1,6 @@
--- /dev/null
+++ b/src/main/java/me/earthme/lophine/config/LophineConfig.java
@@ -1,0 +_,32 @@
@@ -1,0 +_,33 @@
+package me.earthme.lophine.config;
+
+import me.earthme.luminol.config.LuminolConfig;
@@ -15,9 +15,10 @@
+ private static final File luminolConfigFolder = new File("luminol_config");
+ private static final File lophineConfigFolder = new File("lophine_config");
+
+ public static void initConfigs() {
+ public static void initConfigs() throws IOException {
+ configfiles.put("luminol", new LuminolConfig(luminolConfigFolder, "luminol", "me.earthme.luminol.config.modules"));
+ configfiles.put("lophine", new LuminolConfig(lophineConfigFolder, "lophine", "me.earthme.lophine.config.modules"));
+ preLoad();
+ }
+
+ public static void preLoad() throws IOException {
@@ -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";
+ }
+}
@@ -0,0 +1,270 @@
--- /dev/null
+++ b/src/main/java/me/earthme/lophine/utils/LocalLangUtil.java
@@ -1,0 +_,267 @@
+package me.earthme.lophine.utils;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import net.minecraft.locale.DeprecatedTranslationsInfo;
+import net.minecraft.locale.Language;
+import net.minecraft.network.chat.FormattedText;
+import net.minecraft.network.chat.Style;
+import net.minecraft.util.FormattedCharSequence;
+import net.minecraft.util.StringDecomposer;
+
+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.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.logging.Logger;
+
+public class LocalLangUtil {
+ final static Logger logger = Logger.getLogger("LangLoader");
+ final static String VERSION = "1.21.5";
+ final static String basePath = "cache/lophine/" + VERSION + "/";
+ static String lang = me.earthme.lophine.config.modules.optimizations.LanguageConfig.lang;
+
+ public static void init() {
+ init(true);
+ }
+
+ public static void init(boolean init) {
+ if (Objects.equals(lang, "en_us")) return;
+ CompletableFuture.runAsync(() -> {
+ try {
+ String path = basePath + "lang/" + lang + ".json";
+ if (!Files.exists(Path.of(path))) {
+ downloadLangAndCheck();
+ }
+ try {
+ loadLocalLang(path, init);
+ } catch (JsonParseException e) {
+ cleanCache();
+ if (init) {
+ init(false);
+ }
+ }
+ } catch (Exception e) {
+ logger.severe(() -> "Async initialization failed: " + e.getMessage());
+ }
+ });
+ }
+
+ private static void downloadLangAndCheck() {
+ String path = basePath + VERSION + ".json";
+ JsonObject json;
+ if (Files.exists(Path.of(path))) {
+ try {
+ json = loadJson(path);
+ } 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;
+ }
+
+ private static void cleanCache() {
+ Path cachePath = Paths.get(basePath);
+ try {
+ if (Files.exists(cachePath)) {
+ Files.walk(cachePath)
+ .sorted(Comparator.reverseOrder())
+ .forEach(path -> {
+ try {
+ Files.deleteIfExists(path);
+ } catch (IOException e) {
+ logger.warning("Failed to delete: " + path + " - " + e.getMessage());
+ }
+ });
+ logger.info("Cache cleaned: " + cachePath);
+ } else {
+ logger.info("Cache directory does not exist: " + cachePath);
+ }
+ } catch (IOException e) {
+ logger.severe("Cache cleanup failed: " + e.getMessage());
+ }
+ }
+
+
+ public static JsonObject loadJson(String path) throws IOException {
+ byte[] data = Files.readAllBytes(Paths.get(path));
+ return JsonParser.parseString(new String(data)).getAsJsonObject();
+ }
+
+ public static void loadLocalLang(String lang, boolean init) throws IOException {
+ try {
+ Language.inject(load(lang));
+ } catch (Exception e) {
+ logger.warning("Failed to load language file for " + lang + "\n" + e);
+ logger.info("Load default en_us instead of local lang " + lang);
+ if (init) {
+ throw e;
+ } else {
+ Language.inject(Language.loadDefault());
+ }
+ }
+ }
+
+ private static Language load(String lang) throws IOException {
+ DeprecatedTranslationsInfo deprecatedTranslationsInfo = DeprecatedTranslationsInfo.loadFromDefaultResource();
+ Map<String, String> map = new HashMap<>();
+ BiConsumer<String, String> biConsumer = map::put;
+ Language.parseTranslations(biConsumer, "/assets/minecraft/lang/en_us.json");
+ parseTranslations(biConsumer, lang);
+ deprecatedTranslationsInfo.applyToMap(map);
+ final Map<String, String> map1 = Map.copyOf(map);
+ return new Language() {
+ @Override
+ public String getOrDefault(String key, String defaultValue) {
+ return map1.getOrDefault(key, defaultValue);
+ }
+
+ @Override
+ public boolean has(String id) {
+ return map1.containsKey(id);
+ }
+
+ @Override
+ public boolean isDefaultRightToLeft() {
+ return false;
+ }
+
+ @Override
+ public FormattedCharSequence getVisualOrder(FormattedText text) {
+ return sink -> text.visit(
+ (style, content) -> StringDecomposer.iterateFormatted(content, style, sink) ? Optional.empty() : FormattedText.STOP_ITERATION,
+ Style.EMPTY
+ )
+ .isPresent();
+ }
+ };
+ }
+
+ public static void parseTranslations(BiConsumer<String, String> output, String languagePath) throws IOException {
+ try {
+ Path filePath = Paths.get(languagePath);
+ try (InputStream fileStream = Files.newInputStream(filePath)) {
+ Language.loadFromJson(fileStream, output);
+ } catch (java.nio.file.NoSuchFileException fileEx) {
+ logger.warning("Language file not found in both locations: " + languagePath);
+ throw fileEx;
+ } catch (Exception fileEx) {
+ logger.warning("Failed to load from filesystem " + filePath + "\n" + fileEx);
+ throw fileEx;
+ }
+ } catch (Exception ep) {
+ logger.warning("Couldn't read strings from " + languagePath + "\n" + ep);
+ throw ep;
+ }
+ }
+}