diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index f35b7c899..bef641313 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -1,4 +1,5 @@ import org.jackhuang.hmcl.gradle.l10n.CheckTranslations +import org.jackhuang.hmcl.gradle.l10n.CreateLocaleNames import org.jackhuang.hmcl.gradle.l10n.UpsideDownTranslate import org.jackhuang.hmcl.gradle.mod.ParseModDataTask import java.net.URI @@ -205,9 +206,16 @@ tasks.shadowJar { } } +val generatedDir = layout.buildDirectory.dir("generated") + val upsideDownTranslate by tasks.registering(UpsideDownTranslate::class) { inputFile.set(layout.projectDirectory.file("src/main/resources/assets/lang/I18N.properties")) - outputFile.set(layout.buildDirectory.file("generated/i18n/I18N_en_Qabs.properties")) + outputFile.set(generatedDir.map { it.file("generated/i18n/I18N_en_Qabs.properties") }) +} + +val createLocaleNames by tasks.registering(CreateLocaleNames::class) { + languagesFile.set(layout.projectDirectory.file("src/main/resources/assets/lang/languages.json")) + outputDirectory.set(generatedDir.map { it.dir("generated/LocaleNames") }) } tasks.processResources { @@ -221,7 +229,9 @@ tasks.processResources { into("assets/lang") { from(upsideDownTranslate.map { it.outputFile }) + from(createLocaleNames.map { it.outputDirectory }) } + } val makeExecutables by tasks.registering { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java index bded673b9..a963349b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/Locales.java @@ -42,15 +42,18 @@ public final class Locales { public static final SupportedLocale DEFAULT = new SupportedLocale(); public static List getSupportedLocales() { + List list = new ArrayList<>(); + list.add(DEFAULT); + InputStream locales = Locales.class.getResourceAsStream("/assets/lang/languages.json"); if (locales != null) { try (locales) { - return JsonUtils.fromNonNullJsonFully(locales, JsonUtils.listTypeOf(SupportedLocale.class)); + list.addAll(JsonUtils.fromNonNullJsonFully(locales, JsonUtils.listTypeOf(SupportedLocale.class))); } catch (Throwable e) { LOG.warning("Failed to load languages.json", e); } } - return List.of(DEFAULT); + return List.copyOf(list); } private static final ConcurrentMap LOCALES = new ConcurrentHashMap<>(); @@ -60,10 +63,10 @@ public final class Locales { } public static SupportedLocale getLocaleByName(String name) { - if (name == null || "def".equals(name) || "default".equals(name)) + if (name == null || name.isEmpty() || "def".equals(name) || "default".equals(name)) return DEFAULT; - return getLocale(Locale.forLanguageTag(name)); + return getLocale(Locale.forLanguageTag(name.trim())); } @JsonAdapter(SupportedLocale.TypeAdapter.class) diff --git a/HMCL/src/main/resources/assets/lang/languages.json b/HMCL/src/main/resources/assets/lang/languages.json index 2ed2c2b12..01cc511c6 100644 --- a/HMCL/src/main/resources/assets/lang/languages.json +++ b/HMCL/src/main/resources/assets/lang/languages.json @@ -1,5 +1,4 @@ [ - "default", "en", "en-Qabs", "es", diff --git a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLocaleNames.java b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLocaleNames.java index 050be773c..a80c4aae8 100644 --- a/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLocaleNames.java +++ b/buildSrc/src/main/java/org/jackhuang/hmcl/gradle/l10n/CreateLocaleNames.java @@ -17,28 +17,51 @@ */ package org.jackhuang.hmcl.gradle.l10n; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.stream.Collectors; /// @author Glavo public abstract class CreateLocaleNames extends DefaultTask { + @InputFile + public abstract RegularFileProperty getLanguagesFile(); + @OutputDirectory public abstract DirectoryProperty getOutputDirectory(); + private static String mapToFileName(String base, String ext, Locale locale) { + if (locale.getLanguage().isEmpty() || locale.equals(Locale.ENGLISH)) + return base + "." + ext; + else if (locale.toLanguageTag().equals("zh-Hans")) + return base + "_zh." + ext; + else + return base + "_" + locale.toLanguageTag().replace('-', '_') + "." + ext; + } + @TaskAction public void run() throws IOException { + Path languagesFile = getLanguagesFile().get().getAsFile().toPath(); Path outputDir = getOutputDirectory().get().getAsFile().toPath(); if (Files.isDirectory(outputDir)) { @@ -60,6 +83,95 @@ public abstract class CreateLocaleNames extends DefaultTask { Files.deleteIfExists(outputDir); Files.createDirectories(outputDir); + List supportedLanguages; + try (var reader = Files.newBufferedReader(languagesFile)) { + supportedLanguages = new Gson().fromJson(reader, new TypeToken>() { + }).stream() + .map(Locale::forLanguageTag) + .toList(); + } + SortedSet languages = supportedLanguages.stream() + .map(Locale::getLanguage) + .filter(it -> !it.isBlank()) + .collect(Collectors.toCollection(TreeSet::new)); + + SortedSet scripts = supportedLanguages.stream() + .map(Locale::getScript) + .filter(it -> !it.isBlank()) + .collect(Collectors.toCollection(TreeSet::new)); + + for (Locale currentLanguage : supportedLanguages) { + InputStream overrideFile = CreateLocaleNames.class.getResourceAsStream( + mapToFileName("LocaleNamesOverride", "properties", currentLanguage)); + + Properties overrideProperties = new Properties(); + if (overrideFile != null) { + try (var reader = new InputStreamReader(overrideFile, StandardCharsets.UTF_8)) { + overrideProperties.load(reader); + } + } + + Path targetFile = outputDir.resolve(mapToFileName("LocaleNames", "properties", currentLanguage)); + if (Files.exists(targetFile)) + throw new GradleException(String.format("File %s already exists", targetFile)); + + try (var writer = Files.newBufferedWriter(targetFile)) { + writer.write(""" + # + # 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 . + # + + """); + + writer.write("# Languages\n"); + for (String language : languages) { + String displayName = overrideProperties.getProperty(language); + if (displayName == null) { + displayName = new Locale.Builder() + .setLanguage(language) + .build() + .getDisplayLanguage(currentLanguage); + + if (displayName.equals(language)) + continue; // Skip + } + + writer.write(language + "=" + displayName + "\n"); + } + writer.write('\n'); + + writer.write("# Scripts\n"); + for (String script : scripts) { + String displayName = overrideProperties.getProperty(script); + if (displayName == null) { + displayName = new Locale.Builder() + .setScript(script) + .build() + .getDisplayScript(currentLanguage); + + if (displayName.equals(script)) + continue; // Skip + } + + writer.write(script + "=" + displayName + "\n"); + } + writer.write('\n'); + } + } } } diff --git a/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride.properties b/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride.properties new file mode 100644 index 000000000..d0c175467 --- /dev/null +++ b/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride.properties @@ -0,0 +1,23 @@ +# +# 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 . +# + +# Languages +lzh=Classical Chinese + +# Scripts +Qabs=Upside down \ No newline at end of file diff --git a/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_zh.properties b/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_zh.properties new file mode 100644 index 000000000..3e0229f40 --- /dev/null +++ b/buildSrc/src/main/resources/org/jackhuang/hmcl/gradle/l10n/LocaleNamesOverride_zh.properties @@ -0,0 +1,23 @@ +# +# 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 . +# + +# Languages +lzh=文言 + +# Scripts +Qabs=颠倒 \ No newline at end of file