优化 checkTranslations Task (#4396)

This commit is contained in:
Glavo 2025-09-06 15:41:38 +08:00 committed by GitHub
parent fe59f495f6
commit 09c17fc85b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 194 additions and 54 deletions

View File

@ -1,3 +1,4 @@
import org.jackhuang.hmcl.gradle.CheckTranslations
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
import java.net.URI import java.net.URI
import java.nio.file.FileSystems import java.nio.file.FileSystems
@ -332,6 +333,17 @@ tasks.register<JavaExec>("run") {
} }
} }
// Check Translations
tasks.register<CheckTranslations>("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 // mcmod data
tasks.register<ParseModDataTask>("parseModData") { tasks.register<ParseModDataTask>("parseModData") {

View File

@ -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) org.jackhuang.hmcl.gradle.javafx.JavaFXUtils.register(rootProject)
defaultTasks("clean", "build") defaultTasks("clean", "build")

View File

@ -8,6 +8,6 @@ dependencies {
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }

View File

@ -0,0 +1,180 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
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<String, String> consumer) {
properties.forEach((key, value) -> consumer.accept(key.toString(), value.toString()));
}
}
private static final class Checker {
private final Map<PropertiesFile, Map<Class<?>, Set<Problem>>> 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);
}
}
}
}