This commit is contained in:
Glavo 2025-09-21 20:56:36 +08:00
parent b68d8a4be0
commit 7ed3462e92
5 changed files with 125 additions and 72 deletions

View File

@ -65,7 +65,7 @@ public final class SettingsPage extends SettingsView {
FXUtils.smoothScrolling(scroll);
// ==== Languages ====
cboLanguage.getItems().setAll(Locales.LOCALES);
cboLanguage.getItems().setAll(Locales.getSupportedLocales());
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());

View File

@ -19,14 +19,19 @@ package org.jackhuang.hmcl.util.i18n;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.IOException;
import java.io.InputStream;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@ -34,71 +39,31 @@ public final class Locales {
private Locales() {
}
public static final SupportedLocale DEFAULT = new SupportedLocale();
public static final SupportedLocale DEFAULT;
static {
String language = System.getenv("HMCL_LANGUAGE");
DEFAULT = new SupportedLocale(true, "def",
StringUtils.isBlank(language) ? LocaleUtils.SYSTEM_DEFAULT : Locale.forLanguageTag(language));
public static List<SupportedLocale> getSupportedLocales() {
InputStream locales = Locales.class.getResourceAsStream("/assets/lang/locales.json");
if (locales != null) {
try (locales) {
return JsonUtils.fromNonNullJsonFully(locales, JsonUtils.listTypeOf(SupportedLocale.class));
} catch (Throwable e) {
LOG.warning("Failed to load locales.json", e);
}
}
return List.of(DEFAULT);
}
/**
* English
*/
public static final SupportedLocale EN = new SupportedLocale("en");
private static final ConcurrentMap<Locale, SupportedLocale> LOCALES = new ConcurrentHashMap<>();
/**
* ɥsᴉꞁᵷuƎ (uʍoᗡ ǝpᴉsd)
*/
public static final SupportedLocale EN_QABS = new SupportedLocale("en-Qabs");
/**
* Spanish
*/
public static final SupportedLocale ES = new SupportedLocale("es");
/**
* Russian
*/
public static final SupportedLocale RU = new SupportedLocale("ru");
/**
* Ukrainian
*/
public static final SupportedLocale UK = new SupportedLocale("uk");
/**
* Japanese
*/
public static final SupportedLocale JA = new SupportedLocale("ja");
/**
* Chinese (Simplified)
*/
public static final SupportedLocale ZH_HANS = new SupportedLocale("zh_CN", LocaleUtils.LOCALE_ZH_HANS);
/**
* Chinese (Traditional)
*/
public static final SupportedLocale ZH_HANT = new SupportedLocale("zh", LocaleUtils.LOCALE_ZH_HANT);
/**
* Wenyan (Classical Chinese)
*/
public static final SupportedLocale WENYAN = new SupportedLocale("lzh");
public static final List<SupportedLocale> LOCALES = List.of(DEFAULT, EN, EN_QABS, ES, JA, RU, UK, ZH_HANS, ZH_HANT, WENYAN);
public static SupportedLocale getLocale(Locale locale) {
return LOCALES.computeIfAbsent(locale, SupportedLocale::new);
}
public static SupportedLocale getLocaleByName(String name) {
if (name == null) return DEFAULT;
if (name == null || "def".equals(name) || "default".equals(name))
return DEFAULT;
for (SupportedLocale locale : LOCALES) {
if (locale.getName().equalsIgnoreCase(name))
return locale;
}
return DEFAULT;
return getLocale(Locale.forLanguageTag(name));
}
@JsonAdapter(SupportedLocale.TypeAdapter.class)
@ -107,23 +72,26 @@ public final class Locales {
private final String name;
private final Locale locale;
private ResourceBundle resourceBundle;
private ResourceBundle localeNamesBundle;
private DateTimeFormatter dateTimeFormatter;
private List<Locale> candidateLocales;
SupportedLocale(boolean isDefault, String name, Locale locale) {
this.isDefault = isDefault;
this.name = name;
SupportedLocale() {
this.isDefault = true;
this.name = "def"; // TODO: Change to "default" after updating the Config format
String language = System.getenv("HMCL_LANGUAGE");
this.locale = StringUtils.isBlank(language)
? LocaleUtils.SYSTEM_DEFAULT
: Locale.forLanguageTag(language);
}
SupportedLocale(Locale locale) {
this.isDefault = false;
this.name = locale.toLanguageTag();
this.locale = locale;
}
SupportedLocale(String name) {
this(false, name, Locale.forLanguageTag(name));
}
SupportedLocale(String name, Locale locale) {
this(false, name, locale);
}
public boolean isDefault() {
return isDefault;
}
@ -279,7 +247,16 @@ public final class Locales {
@Override
public SupportedLocale read(JsonReader in) throws IOException {
return getLocaleByName(in.nextString());
if (in.peek() == JsonToken.NULL)
return DEFAULT;
String language = in.nextString();
return getLocaleByName(switch (language) {
// TODO: Remove these compatibility codes after updating the Config format
case "zh_CN" -> "zh-Hans"; // For compatibility
case "zh" -> "zh-Hant"; // For compatibility
default -> language;
});
}
}
}

View File

@ -41,7 +41,7 @@ public final class MinecraftWiki {
translatedVersion = WenyanUtils.translateGameVersion(gameVersion);
if (translatedVersion.equals(gameVersion.toString()) || gameVersion instanceof GameVersionNumber.Old) {
return getWikiLink(Locales.ZH_HANT, version);
return getWikiLink(Locales.getLocale(LocaleUtils.LOCALE_ZH_HANT), version);
} else if (SNAPSHOT_PATTERN.matcher(wikiVersion).matches()) {
return locale.i18n("wiki.version.game.snapshot", translatedVersion);
} else {

View File

@ -0,0 +1,12 @@
[
"default",
"en",
"en-Qabs",
"es",
"ru",
"uk",
"ja",
"zh-Hans",
"zh-Hant",
"lzh"
]

View File

@ -0,0 +1,64 @@
/*
* 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.l10n;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
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.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public abstract class CreateLocaleNames extends DefaultTask {
@OutputDirectory
public abstract DirectoryProperty getOutputDirectory();
@TaskAction
public void run() throws IOException {
Path outputDir = getOutputDirectory().get().getAsFile().toPath();
if (Files.isDirectory(outputDir)) {
Files.walkFileTree(outputDir, new SimpleFileVisitor<>() {
@Override
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException {
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public @NotNull FileVisitResult postVisitDirectory(@NotNull Path dir, @Nullable IOException exc) throws IOException {
if (!dir.equals(outputDir))
Files.deleteIfExists(dir);
return FileVisitResult.CONTINUE;
}
});
}
Files.deleteIfExists(outputDir);
Files.createDirectories(outputDir);
}
}