From f45e0abbf43c10963ba5cd8a729f7e4d83729970 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sat, 11 Sep 2021 20:08:38 +0800 Subject: [PATCH] feat(mod): Chinese translations for mod names. --- .../hmcl/ui/versions/ModDownloadListPage.java | 4 +- .../hmcl/ui/versions/ModDownloadPage.java | 3 +- .../hmcl/ui/versions/ModListPageSkin.java | 42 +++-- .../hmcl/ui/versions/ModTranslations.java | 165 ++++++++++++++++++ HMCL/src/main/resources/assets/mod_data.txt | 7 + .../jackhuang/hmcl/mod/FabricModMetadata.java | 8 +- .../hmcl/mod/ForgeNewModMetadata.java | 2 +- .../hmcl/mod/ForgeOldModMetadata.java | 2 +- .../jackhuang/hmcl/mod/LiteModMetadata.java | 2 +- .../java/org/jackhuang/hmcl/mod/ModInfo.java | 12 +- .../org/jackhuang/hmcl/mod/ModManager.java | 2 +- .../org/jackhuang/hmcl/mod/PackMcMeta.java | 2 +- 12 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java index 9d72d06fb..c484a26b5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadListPage.java @@ -138,6 +138,7 @@ public class ModDownloadListPage extends Control implements DecoratorPage, Versi setLoading(false); if (exception == null) { items.setAll(result); + failed.set(false); } else { failed.set(true); } @@ -301,7 +302,8 @@ public class ModDownloadListPage extends Control implements DecoratorPage, Versi @Override protected void updateControl(CurseAddon dataItem, boolean empty) { if (empty) return; - content.setTitle(dataItem.getName()); + ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(dataItem.getSlug()); + content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getName()); content.setSubtitle(dataItem.getSummary()); content.getTags().setAll(dataItem.getCategories().stream() .map(category -> i18n("curse.category." + category.getCategoryId())) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java index 9270b9d8e..bdb914ddb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModDownloadPage.java @@ -185,7 +185,8 @@ public class ModDownloadPage extends Control implements DecoratorPage { TwoLineListItem content = new TwoLineListItem(); HBox.setHgrow(content, Priority.ALWAYS); - content.setTitle(getSkinnable().addon.getName()); + ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug()); + content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getName()); content.setSubtitle(getSkinnable().addon.getSummary()); content.getTags().setAll(getSkinnable().addon.getCategories().stream() .map(category -> i18n("curse.category." + category.getCategoryId())) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index b1face0c8..37186a21a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -43,6 +43,7 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; @@ -53,6 +54,7 @@ import java.io.ByteArrayOutputStream; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Locale; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; @@ -122,6 +124,7 @@ class ModListPageSkin extends SkinBase { private final BooleanProperty active; private final ModInfo modInfo; private final String message; + private final ModTranslations.Mod mod; ModInfoObject(ModInfo modInfo) { this.modInfo = modInfo; @@ -134,6 +137,7 @@ class ModListPageSkin extends SkinBase { if (isNotBlank(modInfo.getAuthors())) message.append(", ").append(i18n("archive.author")).append(": ").append(modInfo.getAuthors()); this.message = message.toString(); + this.mod = ModTranslations.getModById(modInfo.getId()); } String getTitle() { @@ -148,6 +152,10 @@ class ModListPageSkin extends SkinBase { return modInfo; } + public ModTranslations.Mod getMod() { + return mod; + } + @Override public int compareTo(@NotNull ModListPageSkin.ModInfoObject o) { return modInfo.getFileName().toLowerCase().compareTo(o.modInfo.getFileName().toLowerCase()); @@ -201,15 +209,24 @@ class ModListPageSkin extends SkinBase { JFXButton searchButton = new JFXButton(); searchButton.getStyleClass().add("dialog-cancel"); - searchButton.setText(i18n("mods.mcmod.search")); - searchButton.setOnAction(e -> { - fireEvent(new DialogCloseEvent()); - FXUtils.openLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf( - pair("key", modInfo.getModInfo().getName()), - pair("site", "all"), - pair("filter", "0") - ))); - }); + + if (modInfo.getMod() == null || StringUtils.isBlank(modInfo.getMod().getMcmod())) { + searchButton.setText(i18n("mods.mcmod.search")); + searchButton.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + FXUtils.openLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf( + pair("key", modInfo.getModInfo().getName()), + pair("site", "all"), + pair("filter", "0") + ))); + }); + } else { + searchButton.setText(i18n("mods.mcmod.page")); + searchButton.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + FXUtils.openLink("https://www.mcmod.cn/class/" + modInfo.getMod().getMcmod() + ".html"); + }); + } if (StringUtils.isNotBlank(modInfo.getModInfo().getUrl())) { JFXButton officialPageButton = new JFXButton(); @@ -259,7 +276,12 @@ class ModListPageSkin extends SkinBase { @Override protected void updateControl(ModInfoObject dataItem, boolean empty) { if (empty) return; - content.setTitle(dataItem.getTitle()); + if (dataItem.getMod() != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA) { + content.setTitle(dataItem.getMod().getDisplayName()); + content.getTags().setAll(dataItem.getTitle()); + } else { + content.setTitle(dataItem.getTitle()); + } content.setSubtitle(dataItem.getSubtitle()); if (booleanProperty != null) { checkBox.selectedProperty().unbindBidirectional(booleanProperty); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java new file mode 100644 index 000000000..6452588be --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java @@ -0,0 +1,165 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.io.IOUtils; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import static org.jackhuang.hmcl.util.Logging.LOG; + +/** + * Parser for mod_data.txt + * + * @see mcmod.cn + */ +public final class ModTranslations { + private static List mods; + private static Map modIdMap; // mod id -> mod + private static Map curseForgeMap; // curseforge id -> mod + + private ModTranslations(){} + + public static Mod getModByCurseForgeId(String id) { + if (StringUtils.isBlank(id) || !loadCurseForgeMap()) return null; + + return curseForgeMap.get(id); + } + + public static Mod getModById(String id) { + if (StringUtils.isBlank(id) || !loadModIdMap()) return null; + + return modIdMap.get(id); + } + + private static boolean loadFromResource() { + if (mods != null) return true; + try { + String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream("/assets/mod_data.txt"), StandardCharsets.UTF_8); + mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList()); + return true; + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to load /assets/mod_data.txt", e); + return false; + } + } + + private static boolean loadCurseForgeMap() { + if (curseForgeMap != null) { + return true; + } + + if (mods == null) { + if (!loadFromResource()) return false; + } + + curseForgeMap = new HashMap<>(); + for (Mod mod : mods) { + if (StringUtils.isNotBlank(mod.getCurseforge())) { + curseForgeMap.put(mod.getCurseforge(), mod); + } + } + return true; + } + + private static boolean loadModIdMap() { + if (modIdMap != null) { + return true; + } + + if (mods == null) { + if (!loadFromResource()) return false; + } + + modIdMap = new HashMap<>(); + for (Mod mod : mods) { + for (String id : mod.getModIds()) { + modIdMap.put(id, mod); + } + } + return true; + } + + public static class Mod { + private final String curseforge; + private final String mcmod; + private final String mcbbs; + private final List modIds; + private final String name; + private final String subname; + + public Mod(String line) { + String[] items = line.split(";", -1); + if (items.length != 6) { + throw new IllegalArgumentException("Illegal mod data line, 6 items expected " + line); + } + + curseforge = items[0]; + mcmod = items[1]; + mcbbs = items[2]; + modIds = Collections.unmodifiableList(Arrays.asList(items[3].split(","))); + name = items[4]; + subname = items[5]; + } + + public Mod(String curseforge, String mcmod, String mcbbs, List modIds, String name, String subname) { + this.curseforge = curseforge; + this.mcmod = mcmod; + this.mcbbs = mcbbs; + this.modIds = modIds; + this.name = name; + this.subname = subname; + } + + public String getDisplayName() { + if (StringUtils.isBlank(subname)) { + return name; + } else { + return String.format("%s (%s)", name, subname); + } + } + + public String getCurseforge() { + return curseforge; + } + + public String getMcmod() { + return mcmod; + } + + public String getMcbbs() { + return mcbbs; + } + + public List getModIds() { + return modIds; + } + + public String getName() { + return name; + } + + public String getSubname() { + return subname; + } + } +} diff --git a/HMCL/src/main/resources/assets/mod_data.txt b/HMCL/src/main/resources/assets/mod_data.txt index 942cf486b..642130dc4 100644 --- a/HMCL/src/main/resources/assets/mod_data.txt +++ b/HMCL/src/main/resources/assets/mod_data.txt @@ -1,3 +1,10 @@ +# +# Hello Minecraft! Launcher +# Copyright (C) 2021 huangyuhui and contributors +# +# This file is licensed under CC BY-NC-SA 3.0. +# Thanks to mcmod.cn and all contributors. +# industrial-craft;2;515771;IC2,ic2;工业时代2;Industrial Craft 2 ;3;;RedPowerCore,RedPowerBase;红石力量2;RedPower2 BuildCraft;4;884720;BuildCraft|Core,buildcraftlib,buildcraftcore,buildcraftbuilders,buildcrafttransport,buildcraftsilicon,buildcraftfactory,buildcraftrobotics,buildcraftenergy,BuildMod,kamenridercraft4th,BuildCraft|Energy,BuildCraft|Transport,BuildCraft|Factory,BuildCraft|Silicon,BuildCraft|Builders,BuildCraft|Robotics;建筑;BuildCraft diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java index 7335ec93c..16697bff4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/FabricModMetadata.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; @Immutable public final class FabricModMetadata { + private final String id; private final String name; private final String version; private final String description; @@ -50,10 +51,11 @@ public final class FabricModMetadata { private final Map contact; public FabricModMetadata() { - this("", "", "", "", Collections.emptyList(), Collections.emptyMap()); + this("", "", "", "", "", Collections.emptyList(), Collections.emptyMap()); } - public FabricModMetadata(String name, String version, String icon, String description, List authors, Map contact) { + public FabricModMetadata(String id, String name, String version, String icon, String description, List authors, Map contact) { + this.id = id; this.name = name; this.version = version; this.icon = icon; @@ -69,7 +71,7 @@ public final class FabricModMetadata { throw new IOException("File " + modFile + " is not a Fabric mod."); FabricModMetadata metadata = JsonUtils.fromNonNullJson(FileUtils.readText(mcmod), FabricModMetadata.class); String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", ")); - return new ModInfo(modManager, modFile, metadata.name, new ModInfo.Description(metadata.description), + return new ModInfo(modManager, modFile, metadata.id, metadata.name, new ModInfo.Description(metadata.description), authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "", metadata.icon); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java index 64fc275c0..e1651a26c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeNewModMetadata.java @@ -135,7 +135,7 @@ public final class ForgeNewModMetadata { LOG.log(Level.WARNING, "Failed to parse MANIFEST.MF in file " + modFile.getPath()); } } - return new ModInfo(modManager, modFile, mod.getDisplayName(), new ModInfo.Description(mod.getDescription()), + return new ModInfo(modManager, modFile, mod.getModId(), mod.getDisplayName(), new ModInfo.Description(mod.getDescription()), mod.getAuthors(), mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java index 6aa60f7ff..3ec883a1a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ForgeOldModMetadata.java @@ -138,7 +138,7 @@ public final class ForgeOldModMetadata { authors = String.join(", ", metadata.getAuthorList()); if (StringUtils.isBlank(authors)) authors = metadata.getCredits(); - return new ModInfo(modManager, modFile, metadata.getName(), new ModInfo.Description(metadata.getDescription()), + return new ModInfo(modManager, modFile, metadata.getModId(), metadata.getName(), new ModInfo.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java index 9d737736f..113cbc900 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/LiteModMetadata.java @@ -116,7 +116,7 @@ public final class LiteModMetadata { LiteModMetadata metadata = JsonUtils.GSON.fromJson(IOUtils.readFullyAsString(zipFile.getInputStream(entry)), LiteModMetadata.class); if (metadata == null) throw new IOException("Mod " + modFile + " `litemod.json` is malformed."); - return new ModInfo(modManager, modFile, metadata.getName(), new ModInfo.Description(metadata.getDescription()), metadata.getAuthor(), + return new ModInfo(modManager, modFile, null, metadata.getName(), new ModInfo.Description(metadata.getDescription()), metadata.getAuthor(), metadata.getVersion(), metadata.getGameVersion(), metadata.getUpdateURI(), ""); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java index 7bd89874e..ce4bb68ed 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModInfo.java @@ -38,6 +38,7 @@ import java.util.logging.Level; public final class ModInfo implements Comparable { private Path file; + private final String id; private final String name; private final Description description; private final String authors; @@ -48,12 +49,13 @@ public final class ModInfo implements Comparable { private final String logoPath; private final BooleanProperty activeProperty; - public ModInfo(ModManager modManager, File file, String name, Description description) { - this(modManager, file, name, description, "", "", "", "", ""); + public ModInfo(ModManager modManager, File file, String id, String name, Description description) { + this(modManager, file, id, name, description, "", "", "", "", ""); } - public ModInfo(ModManager modManager, File file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { + public ModInfo(ModManager modManager, File file, String id, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) { this.file = file.toPath(); + this.id = id; this.name = name; this.description = description; this.authors = authors; @@ -85,6 +87,10 @@ public final class ModInfo implements Comparable { return file; } + public String getId() { + return id; + } + public String getName() { return name; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 098619773..901dea770 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -99,7 +99,7 @@ public final class ModManager { default: throw new IllegalArgumentException("File " + modFile + " is not a mod file."); } - return new ModInfo(this, modFile, FileUtils.getNameWithoutExtension(modFile), new ModInfo.Description(description)); + return new ModInfo(this, modFile, null, FileUtils.getNameWithoutExtension(modFile), new ModInfo.Description(description)); } public void refreshMods() throws IOException { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java index cdf29617b..7bf970049 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/PackMcMeta.java @@ -150,7 +150,7 @@ public class PackMcMeta implements Validation { if (Files.notExists(mcmod)) throw new IOException("File " + modFile + " is not a resource pack."); PackMcMeta metadata = JsonUtils.fromNonNullJson(FileUtils.readText(mcmod), PackMcMeta.class); - return new ModInfo(modManager, modFile, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, "", "", "", "", ""); + return new ModInfo(modManager, modFile, null, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, "", "", "", "", ""); } } }