Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

## 支持的版本

- Minecraft:1.6.1~1.21.11、26.1~26.1.2 都支持
- Minecraft:1.6.1~1.21.11、26.1~26.3 都支持
- Mod加载器:MinecraftForge、NeoForge、Fabric、Quilt 都支持
- Java:8~25 都支持
- Java:8~25 都支持

仅仅需要在mods文件夹中放置本Mod的jar文件即可,Mod本身与各主流Minecraft版本、Mod Loader、Java版本均兼容,Mod本身不需要进行任何版本隔离。

Expand All @@ -36,6 +36,14 @@
- 合并转换:会合并加转换最近版本的一些资源包,尽可能做最大化的支持
- 特别说明:1.13开始将语言文件变化为json格式,所以不能将1.12.2的资源包用于1.13以上,反之同理

## 配置

首次启动会在 `config/i18nupdatemod.json` 生成带说明的模板。

- `cacheTtlHours`:两次联网检查之间的最短间隔,默认 `24` 小时,`0` 表示每次启动都检查。间隔内本地没有变化就完全不联网、不重新组包;新增/删除模组、模组文件更新或修改 `excludeMods` 会立即触发检查。
- `excludeMods`:不参与汉化的模组命名空间(例如 `jei`、`create`),跳过下载、也不会进入汉化包;已有缓存不动。
- `onlyWhenChineseSystem`:为 `true` 时只在系统语言为中文时才启动;非中文系统下本Mod完全不动作(不联网、不下载、不写任何文件)。默认 `false`,整合包作者可设为 `true` 后随包分发。判定读的是操作系统区域设置,不是游戏内语言。

## 开发环境

构建需要 JDK 21(用于编译新版 NeoForge 的服务接口);产物仍使用 Java 8 字节码,旧版游戏的 Java 运行要求不变。
Expand Down
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,17 @@ mapOf(
val bmclBaseUrl = if (variant == "Release") {
"https://bmclapi2.bangbang93.com/mirrors/i18n-update-mod/"
} else ""
val githubBaseUrl = "https://github.com/CFPAOrg/Minecraft-Mod-Language-Package/releases/download/autobuild/"
val configFile = layout.buildDirectory.file("generated/buildConfig/$variant/i18n-build.properties")
val generateConfig = tasks.register("generate${variant}Config") {
inputs.property("assetBaseUrl", baseUrl)
inputs.property("bmclBaseUrl", bmclBaseUrl)
inputs.property("githubBaseUrl", githubBaseUrl)
outputs.file(configFile)
doLast {
configFile.get().asFile.apply {
parentFile.mkdirs()
writeText("assetBaseUrl=$baseUrl\nbmclBaseUrl=$bmclBaseUrl\n")
writeText("assetBaseUrl=$baseUrl\nbmclBaseUrl=$bmclBaseUrl\ngithubBaseUrl=$githubBaseUrl\n")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version=3.7.1
minecraft=1.6.1,1.6.2,1.6.4,1.7.2,1.7.10,1.8,1.8.8,1.8.9,1.9,1.9.4,1.10,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2,1.14,1.14.1,1.14.2,1.14.3,1.14.4,1.15,1.15.1,1.15.2,1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2
minecraft=1.6.1,1.6.2,1.6.4,1.7.2,1.7.10,1.8,1.8.8,1.8.9,1.9,1.9.4,1.10,1.10.2,1.11,1.11.2,1.12,1.12.1,1.12.2,1.13.2,1.14,1.14.1,1.14.2,1.14.3,1.14.4,1.15,1.15.1,1.15.2,1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,1.17,1.17.1,1.18,1.18.1,1.18.2,1.19,1.19.1,1.19.2,1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2,26.2,26.3
curseforge=NeoForge,Forge,Fabric,Quilt,Client,Java 8,Java 9,Java 10,Java 11,Java 12,Java 13,Java 14,Java 15,Java 16,Java 17,Java 18
26 changes: 23 additions & 3 deletions src/main/java/i18nupdatemod/I18nUpdateMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import i18nupdatemod.core.GameConfig;
import i18nupdatemod.core.I18nUserConfig;
import i18nupdatemod.core.ResourcePackUpdater;
import i18nupdatemod.core.RuntimePackActivation;
import i18nupdatemod.entity.ModTranslation;
Expand All @@ -15,6 +16,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.Stream;

Expand All @@ -24,6 +26,22 @@ public class I18nUpdateMod {

public static final Gson GSON = new Gson();

/** Runs before any file-backed logging: loads the user config, applies the system-language gate, opens the log file. Returns false to stay completely idle. */
public static boolean prepare(Path minecraftPath) {
I18nUserConfig.load(minecraftPath);
boolean idle = I18nUserConfig.onlyWhenChineseSystem() && !isChineseSystem();
if (!idle) {
Log.setMinecraftLogFile(minecraftPath);
}
I18nUserConfig.flushLogMessages();
if (idle) {
RuntimePackActivation.disable();
Log.info("System language is not Chinese; I18nUpdateMod stays idle.");
return false;
}
return true;
}

public static void init(Path minecraftPath, String minecraftVersion, String loader, @NotNull List<ModTranslation> mods) {
try (InputStream is = I18nUpdateMod.class.getResourceAsStream("/i18nMetaData.json")) {
MOD_VERSION = GSON.fromJson(new InputStreamReader(is), JsonObject.class).get("version").getAsString();
Expand Down Expand Up @@ -66,18 +84,20 @@ public static void init(Path minecraftPath, String minecraftVersion, String load
}

//Apply resource pack
GameConfig config = new GameConfig(minecraftPath.resolve("options.txt"));
config.addResourcePack("Minecraft-Mod-Language-Modpack",
GameConfig.addResourcePack(minecraftPath.resolve("options.txt"), "Minecraft-Mod-Language-Modpack",
(Version.from(minecraftVersion).compareTo(Version.from("1.13")) < 0 ? "" : "file/")
+ convertedPack.getFileName().toString());
config.writeToFile();
} catch (Exception e) {
Log.warning(String.format("Failed to update resource pack: %s", e));
// e.printStackTrace();
}
}


private static boolean isChineseSystem() {
return "zh".equalsIgnoreCase(Locale.getDefault().getLanguage());
}

public static String getLocalStoragePos(Path minecraftPath) {
Path userHome = Paths.get(System.getProperty("user.home"));
Path oldPath = userHome.resolve("." + MOD_ID);
Expand Down
56 changes: 16 additions & 40 deletions src/main/java/i18nupdatemod/core/GameConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import i18nupdatemod.util.Log;
import org.apache.commons.io.FileUtils;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
Expand All @@ -13,41 +12,35 @@
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GameConfig {
private static final Gson GSON = new Gson();
private static final Type STRING_LIST_TYPE = new TypeToken<List<String>>() {
}.getType();
private static final byte[] RESOURCE_PACKS_KEY =
"resourcePacks".getBytes(StandardCharsets.US_ASCII);
protected Map<String, String> configs = new LinkedHashMap<>();
private final Path configFile;

public GameConfig(Path configFile) throws Exception {
this.configFile = configFile;
if (!Files.exists(configFile)) {
return;
}
this.configs = FileUtils.readLines(configFile.toFile(), StandardCharsets.UTF_8).stream()
.map(it -> it.split(":", 2))
.filter(it -> it.length == 2)
.collect(Collectors.toMap(it -> it[0], it -> it[1], (a, b) -> a, LinkedHashMap::new));
private GameConfig() {
}

public void writeToFile() throws Exception {
FileUtils.writeLines(configFile.toFile(), "UTF-8", configs.entrySet().stream()
.map(it -> it.getKey() + ":" + it.getValue()).collect(Collectors.toList()));
public static void addResourcePack(Path configFile, String baseName, String resourcePack)
throws Exception {
List<String> resourcePacks = new ArrayList<>(readResourcePacks(configFile));
if (resourcePacks.contains(resourcePack)) {
return;
}
// 换版本时清掉旧的同类包
resourcePacks.removeIf(it -> it.contains(baseName));
resourcePacks.add(resourcePack);
updateResourcePacks(configFile, resourcePacks);
Log.info("Resource Packs: %s", resourcePacks);
}

/**
* 避免NeoForge修改资源包顺序导致的按键配置丢失问题。
*/
/** 避免NeoForge修改资源包顺序导致的按键配置丢失问题。 */
static void updateResourcePacks(Path configFile, List<?> resourcePacks)
throws Exception {
Path absolute = configFile.toAbsolutePath().normalize();
Expand Down Expand Up @@ -115,10 +108,8 @@ static List<String> readResourcePacks(Path configFile) throws Exception {
}
List<String> selected = GSON.fromJson(
new String(bytes, valueStart, valueEnd - valueStart, StandardCharsets.UTF_8), STRING_LIST_TYPE);
if (selected == null) {
throw new IllegalStateException("resourcePacks must be a JSON array");
}
return selected;
// 空值(如 `resourcePacks:` 后面什么都没有)Gson 返回 null,等同于没有任何资源包
return selected == null ? Collections.emptyList() : selected;
}

private static byte[] replaceResourcePacksLines(byte[] original, byte[] encodedLine) {
Expand Down Expand Up @@ -206,20 +197,5 @@ private static boolean containsCrlf(byte[] bytes) {
return false;
}

public void addResourcePack(String baseName, String resourcePack) {
List<String> resourcePacks = GSON.fromJson(
configs.computeIfAbsent("resourcePacks", it -> "[]"), STRING_LIST_TYPE);
//If resource packs already contains target resource pack, nothing to do
if (resourcePacks.contains(resourcePack)) {
return;
}
//Remove other Minecraft Mod Language Pack
resourcePacks = resourcePacks.stream().filter(it -> !it.contains(baseName)).collect(Collectors.toList());
resourcePacks.add(resourcePack);
configs.put("resourcePacks", GSON.toJson(resourcePacks));
Log.info(String.format("Resource Packs: %s", configs.get("resourcePacks")));
// configs.put("lang", "zh_cn");
}

}

Loading
Loading