From 09c17fc85b23dcb1a71e8621b0c7186fed815f0c Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 6 Sep 2025 15:41:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20checkTranslations=20Task?= =?UTF-8?q?=20(#4396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/build.gradle.kts | 12 ++ build.gradle.kts | 52 ----- buildSrc/build.gradle.kts | 4 +- .../hmcl/gradle/CheckTranslations.java | 180 ++++++++++++++++++ 4 files changed, 194 insertions(+), 54 deletions(-) create mode 100644 buildSrc/src/main/java/org/jackhuang/hmcl/gradle/CheckTranslations.java diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 3141fd19e..2d002d65c 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jackhuang.hmcl.gradle.CheckTranslations import org.jackhuang.hmcl.gradle.mod.ParseModDataTask import java.net.URI import java.nio.file.FileSystems @@ -332,6 +333,17 @@ tasks.register("run") { } } +// Check Translations + +tasks.register("checkTranslations") { + val dir = layout.projectDirectory.dir("src/main/resources/assets/lang") + + englishFile.set(dir.file("I18N.properties")) + simplifiedChineseFile.set(dir.file("I18N_zh_CN.properties")) + traditionalChineseFile.set(dir.file("I18N_zh.properties")) + classicalChineseFile.set(dir.file("I18N_lzh.properties")) +} + // mcmod data tasks.register("parseModData") { diff --git a/build.gradle.kts b/build.gradle.kts index 5aa03e82b..fbf39b95d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -57,58 +57,6 @@ subprojects { } } -tasks.register("checkTranslations") { - doLast { - val hmclLangDir = file("HMCL/src/main/resources/assets/lang") - - val en = java.util.Properties().apply { - hmclLangDir.resolve("I18N.properties").bufferedReader().use { load(it) } - } - - val zh = java.util.Properties().apply { - hmclLangDir.resolve("I18N_zh.properties").bufferedReader().use { load(it) } - } - - val zh_CN = java.util.Properties().apply { - hmclLangDir.resolve("I18N_zh_CN.properties").bufferedReader().use { load(it) } - } - - var success = true - - zh_CN.forEach { - if (!en.containsKey(it.key)) { - project.logger.warn("I18N.properties missing key '${it.key}'") - success = false - } - } - - zh_CN.forEach { - if (!zh.containsKey(it.key)) { - project.logger.warn("I18N_zh.properties missing key '${it.key}'") - success = false - } - } - - zh_CN.forEach { - if (it.value.toString().contains("帐户")) { - project.logger.warn("The misspelled '帐户' in '${it.key}' should be replaced by '账户'") - success = false - } - } - - zh_CN.forEach { - if (it.value.toString().contains("其它")) { - project.logger.warn("The misspelled '其它' in '${it.key}' should be replaced by '其他'") - success = false - } - } - - if (!success) { - throw GradleException("Part of the translation is missing") - } - } -} - org.jackhuang.hmcl.gradle.javafx.JavaFXUtils.register(rootProject) defaultTasks("clean", "build") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 4e2c19314..90d7f7e4a 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,6 +8,6 @@ dependencies { } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } \ No newline at end of file diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/CheckTranslations.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/CheckTranslations.java new file mode 100644 index 000000000..c64384681 --- /dev/null +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/CheckTranslations.java @@ -0,0 +1,180 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 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.gradle; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.BiConsumer; + +/// @author Glavo +public abstract class CheckTranslations extends DefaultTask { + + private static final Logger LOGGER = Logging.getLogger(CheckTranslations.class); + + @InputFile + public abstract RegularFileProperty getEnglishFile(); + + @InputFile + public abstract RegularFileProperty getSimplifiedChineseFile(); + + @InputFile + public abstract RegularFileProperty getTraditionalChineseFile(); + + @InputFile + public abstract RegularFileProperty getClassicalChineseFile(); + + @TaskAction + public void run() throws IOException { + Checker checker = new Checker(); + + var english = new PropertiesFile(getEnglishFile()); + var simplifiedChinese = new PropertiesFile(getSimplifiedChineseFile()); + var traditionalChinese = new PropertiesFile(getTraditionalChineseFile()); + var classicalChinese = new PropertiesFile(getClassicalChineseFile()); + + simplifiedChinese.forEach((key, value) -> { + checker.checkKeyExists(english, key); + checker.checkKeyExists(traditionalChinese, key); + + checker.checkMisspelled(simplifiedChinese, key, value, "账户", "帐户"); + checker.checkMisspelled(simplifiedChinese, key, value, "其他", "其它"); + }); + + classicalChinese.forEach((key, value) -> { + checker.checkMisspelled(classicalChinese, key, value, "綫", "線"); + checker.checkMisspelled(classicalChinese, key, value, "爲", "為"); + checker.checkMisspelled(classicalChinese, key, value, "啟", "啓"); + }); + + checker.check(); + } + + private static final class PropertiesFile { + final Path path; + final Properties properties = new Properties(); + + PropertiesFile(RegularFileProperty property) throws IOException { + this(property.getAsFile().get().toPath().toAbsolutePath().normalize()); + } + + PropertiesFile(Path path) throws IOException { + this.path = path; + try (var reader = Files.newBufferedReader(path)) { + properties.load(reader); + } + } + + public String getFileName() { + return path.getFileName().toString(); + } + + public void forEach(BiConsumer consumer) { + properties.forEach((key, value) -> consumer.accept(key.toString(), value.toString())); + } + } + + private static final class Checker { + + private final Map, Set>> problems = new LinkedHashMap<>(); + private int problemsCount; + + public void checkKeyExists(PropertiesFile file, String key) { + if (!file.properties.containsKey(key)) { + onFailure(file, new Problem.MissingKey(key)); + } + } + + public void checkMisspelled(PropertiesFile file, String key, String value, + String correct, String misspelled) { + if (value.contains(misspelled)) { + onFailure(file, new Problem.Misspelled(correct, misspelled)); + } + } + + public void onFailure(PropertiesFile file, Problem problem) { + problemsCount++; + problems.computeIfAbsent(file, ignored -> new HashMap<>()) + .computeIfAbsent(problem.getClass(), ignored -> new LinkedHashSet<>()) + .add(problem); + } + + public void check() { + if (problemsCount > 0) { + problems.forEach((file, problems) -> { + problems.values().stream().flatMap(Collection::stream).forEach(problem -> + LOGGER.warn("{}: {}", file.getFileName(), problem.getMessage())); + }); + + throw new GradleException("Failed to check translations, " + problemsCount + " found problems."); + } + } + } + + private static abstract sealed class Problem { + public abstract String getMessage(); + + private static final class MissingKey extends Problem { + private final String key; + + MissingKey(String key) { + this.key = key; + } + + @Override + public String getMessage() { + return "missing key '%s'".formatted(key); + } + } + + private static final class Misspelled extends Problem { + private final String correct; + private final String misspelled; + + Misspelled(String correct, String misspelled) { + this.correct = correct; + this.misspelled = misspelled; + } + + @Override + public String getMessage() { + return "misspelled '%s' should be replaced by '%s'".formatted(misspelled, correct); + } + + @Override + public int hashCode() { + return misspelled.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Misspelled that && this.misspelled.equals(that.misspelled); + } + } + + } +}