From 3c1d3f36af3cd7b0a78c2132a736f654c568a6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=9E=E5=BA=90?= <109708109+CiiLu@users.noreply.github.com> Date: Sat, 15 Aug 2026 22:50:32 +0800 Subject: [PATCH 1/3] qwq --- HMCL/build.gradle.kts | 1 + .../java/org/jackhuang/hmcl/Metadata.java | 1 + .../hmcl/ui/construct/LineSelectButton.java | 9 +++- .../jackhuang/hmcl/ui/main/SettingsPage.java | 21 ++++++--- .../hmcl/util/i18n/SupportedLocale.java | 44 +++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 1159d354729..4eb01a61a3e 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -128,6 +128,7 @@ tasks.checkstyleMain { val addOpens = listOf( "java.base/java.lang", + "java.base/java.util", "java.base/java.lang.reflect", "java.base/jdk.internal.loader", "javafx.base/com.sun.javafx.binding", diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index c81379dbc5f..ce8bbeaa214 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -57,6 +57,7 @@ private Metadata() { public static final String CHANGELOG_URL = DOCS_URL + "/changelog/"; public static final String EULA_URL = DOCS_URL + "/eula/hmcl.html"; public static final String GROUPS_URL = "https://www.bilibili.com/opus/905435541874409529"; + public static final String LOCALIZATION_URL = "https://github.com/HMCL-dev/HMCL/blob/main/docs/Localization.md"; public static final String BUILD_CHANNEL = JarUtils.getAttribute("hmcl.version.type", "nightly"); public static final String GITHUB_SHA = JarUtils.getAttribute("hmcl.version.hash", null); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineSelectButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineSelectButton.java index e80db45871c..9859635f550 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineSelectButton.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineSelectButton.java @@ -20,7 +20,10 @@ import com.jfoenix.controls.JFXPopup; import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; -import javafx.beans.property.*; +import javafx.beans.property.ListProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.css.PseudoClass; @@ -189,6 +192,10 @@ public void setDescriptionConverter(Function value) { descriptionConverterProperty().set(value); } + public void setNullSafeDescriptionConverter(Function<@NotNull T, String> value) { + descriptionConverterProperty().set(it -> it != null ? value.apply(it) : ""); + } + private final ListProperty items = new SimpleListProperty<>(this, "items", FXCollections.emptyObservableList()); public ListProperty itemsProperty() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index ee8ea5593cf..8c5961d4487 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -173,19 +173,26 @@ protected int getTrailingTextIndex() { chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart")); SupportedLocale currentLocale = I18n.getLocale(); - chooseLanguagePane.setNullSafeConverter(locale -> { + chooseLanguagePane.setNullSafeConverter(it -> it.getDisplayName(currentLocale)); + chooseLanguagePane.setNullSafeDescriptionConverter(locale -> { if (locale.isDefault()) - return locale.getDisplayName(currentLocale); - else if (locale.isSameLanguage(currentLocale)) - return locale.getDisplayName(locale); - else - return locale.getDisplayName(currentLocale) + " - " + locale.getDisplayName(locale); + return ""; + + String name = locale.getDisplayName(locale); + + double completeness = locale.getTranslationCompleteness(); + if (completeness != 0.0) + return String.format("%s - %.2f%%", name, completeness * 100); + else return name; }); chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales()); chooseLanguagePane.valueProperty().bindBidirectional(settings().languageProperty()); - languagePaneList.getContent().add(chooseLanguagePane); + LineButton helpButton = LineButton.createExternalLinkButton(Metadata.LOCALIZATION_URL); + helpButton.setTitle(i18n("settings.launcher.language.contribution.title")); + helpButton.setSubtitle(i18n("settings.launcher.language.contribution.subtitle")); + languagePaneList.getContent().addAll(chooseLanguagePane, helpButton); } rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java index 248e694be00..010f2065a29 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.i18n.translator.Translator; +import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; import java.io.IOException; @@ -294,6 +295,49 @@ public Translator getTranslator() { return this.translator = new Translator(this); } + @Nullable + private static final MethodHandle METHOOD_LOOKUP_HANDLE_KEY_SET; + + static { + MethodHandle methoodLookupHandleKeySet; + try { + methoodLookupHandleKeySet = MethodHandles.privateLookupIn(ResourceBundle.class, MethodHandles.lookup()) + .findVirtual(ResourceBundle.class, "handleKeySet", MethodType.methodType(Set.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + LOG.warning("Failed to get private lookup for ResourceBundle", e); + methoodLookupHandleKeySet = null; + } + METHOOD_LOOKUP_HANDLE_KEY_SET = methoodLookupHandleKeySet; + } + + public double getTranslationCompleteness() { + if (METHOOD_LOOKUP_HANDLE_KEY_SET == null) return 0.0; + + ResourceBundle enBundle = SupportedLocale.getLocale(Locale.ENGLISH).getResourceBundle(); + Set enKeys = enBundle.keySet(); + int totalKeys = enKeys.size(); + if (totalKeys == 0) return 0.0; + + ResourceBundle currentBundle = getResourceBundle(); + Set handleKeys; + + try { + @SuppressWarnings("unchecked") + Set keys = (Set) METHOOD_LOOKUP_HANDLE_KEY_SET.invoke(currentBundle); + handleKeys = keys; + } catch (Throwable e) { + LOG.warning("Failed to get handle keys", e); + return 0.0; + } + + long actualTranslatedCount = handleKeys.stream() + .filter(enKeys::contains) + .count(); + + double v = (double) actualTranslatedCount / totalKeys; + return Math.min(1.0, v); + } + public boolean isSameLanguage(SupportedLocale other) { return LocaleUtils.getRootLanguage(this.getLocale()) .equals(LocaleUtils.getRootLanguage(other.getLocale())); From 98fff017f72b7d221dc4ab960b096b8b38108370 Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:44:23 +0800 Subject: [PATCH 2/3] qwq --- .../main/java/org/jackhuang/hmcl/ui/SVG.java | 2 + .../hmcl/ui/construct/LineRadioButton.java | 50 ++++++++++ .../hmcl/ui/main/LanguageSettingsPage.java | 97 +++++++++++++++++++ .../hmcl/ui/main/LauncherSettingsPage.java | 7 +- .../jackhuang/hmcl/ui/main/SettingsPage.java | 36 ------- .../hmcl/util/i18n/SupportedLocale.java | 19 +++- HMCL/src/main/resources/assets/css/root.css | 12 ++- .../resources/assets/lang/I18N.properties | 4 + .../resources/assets/lang/I18N_ar.properties | 4 + .../resources/assets/lang/I18N_de.properties | 4 + .../resources/assets/lang/I18N_es.properties | 4 + .../resources/assets/lang/I18N_ja.properties | 4 + .../resources/assets/lang/I18N_lzh.properties | 4 + .../resources/assets/lang/I18N_ru.properties | 4 + .../resources/assets/lang/I18N_uk.properties | 4 + .../resources/assets/lang/I18N_zh.properties | 4 + .../assets/lang/I18N_zh_CN.properties | 4 + 17 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineRadioButton.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LanguageSettingsPage.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 9991ac13b85..61fe0071d01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -101,6 +101,7 @@ public enum SVG { PACKAGE2_FILL("M11 21.725v-9.15L3 7.95v8.025q0 .55.2625 1T4 17.7l7 4.025Zm2 0L20 17.7q.475-.275.7375-.725t.2625-1V7.95l-8 4.625v9.15Zm3.975-13.75 2.95-1.725L13 2.275Q12.525 2 12 2t-1 .275L9.025 3.4l7.95 4.575ZM12 10.85l2.975-1.7L7.05 4.55l-3 1.725L12 10.85Z"), PERSON("M12 12Q10.35 12 9.175 10.825T8 8Q8 6.35 9.175 5.175T12 4Q13.65 4 14.825 5.175T16 8Q16 9.65 14.825 10.825T12 12ZM4 20V17.2Q4 16.35 4.4375 15.6375T5.6 14.55Q7.15 13.775 8.75 13.3875T12 13Q13.65 13 15.25 13.3875T18.4 14.55Q19.125 14.925 19.5625 15.6375T20 17.2V20H4ZM6 18H18V17.2Q18 16.925 17.8625 16.7T17.5 16.35Q16.15 15.675 14.775 15.3375T12 15Q10.6 15 9.225 15.3375T6.5 16.35Q6.275 16.475 6.1375 16.7T6 17.2V18ZM12 10Q12.825 10 13.4125 9.4125T14 8Q14 7.175 13.4125 6.5875T12 6Q11.175 6 10.5875 6.5875T10 8Q10 8.825 10.5875 9.4125T12 10ZM12 8ZM12 18Z"), PUBLIC("M12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM11 19.95V18Q10.175 18 9.5875 17.4125T9 16V15L4.2 10.2Q4.125 10.65 4.0625 11.1T4 12Q4 15.025 5.9875 17.3T11 19.95ZM17.9 17.4Q18.925 16.275 19.4625 14.8875T20 12Q20 9.55 18.6375 7.525T15 4.6V5Q15 5.825 14.4125 6.4125T13 7H11V9Q11 9.425 10.7125 9.7125T10 10H8V12H14Q14.425 12 14.7125 12.2875T15 13V16H16Q16.65 16 17.175 16.3875T17.9 17.4Z"), + TRANSLATE("m11.9 22l4.55-12h2.1l4.55 12H21l-1.075-3.05h-4.85L14 22zM4 19l-1.4-1.4l5.05-5.05q-.875-.875-1.588-2T4.75 8h2.1q.5.975 1 1.7t1.2 1.45q.825-.825 1.713-2.313T12.1 6H1V4h7V2h2v2h7v2h-2.9q-.525 1.8-1.575 3.7t-2.075 2.9l2.4 2.45l-.75 2.05l-3.05-3.125zm11.7-1.8h3.6l-1.8-5.1z"), REFRESH("M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z"), REDO("M9.9 19q-2.425 0-4.163-1.575T4 13.5t1.738-3.925T9.9 8h6.3l-2.6-2.6L15 4l5 5l-5 5l-1.4-1.4l2.6-2.6H9.9q-1.575 0-2.738 1T6 13.5T7.163 16T9.9 17H17v2z"), RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material @@ -126,6 +127,7 @@ public enum SVG { UNDO("M7 19v-2h7.1q1.575 0 2.738-1T18 13.5T16.838 11T14.1 10H7.8l2.6 2.6L9 14L4 9l5-5l1.4 1.4L7.8 8h6.3q2.425 0 4.163 1.575T20 13.5t-1.737 3.925T14.1 19z"), VISIBILITY("M12 16q1.875 0 3.1875-1.3125T16.5 11.5 15.1875 8.3125 12 7 8.8125 8.3125 7.5 11.5t1.3125 3.1875T12 16Zm0-1.8q-1.125 0-1.9125-.7875T9.3 11.5t.7875-1.9125T12 8.8q1.125 0 1.9125.7875T14.7 11.5q0 1.125-.7875 1.9125T12 14.2ZM12 19q-3.65 0-6.65-2.0375T1 11.5Q2.35 8.075 5.35 6.0375T12 4q3.65 0 6.65 2.0375T23 11.5q-1.35 3.425-4.35 5.4625T12 19Zm0-7.5ZM12 17q2.825 0 5.1875-1.4875T20.8 11.5q-1.25-2.525-3.6125-4.0125T12 6 6.8125 7.4875 3.2 11.5q1.25 2.525 3.6125 4.0125T12 17Z"), VISIBILITY_OFF("M16.1 13.3l-1.45-1.45q.225-1.175-.675-2.2t-2.325-.8L10.2 7.4q.425-.2.8625-.3T12 7q1.875 0 3.1875 1.3125T16.5 11.5q0 .5-.1.9375t-.3.8625Zm3.2 3.15-1.45-1.4q.95-.725 1.6875-1.5875T20.8 11.5q-1.25-2.525-3.5875-4.0125T12 6q-.725 0-1.425.1T9.2 6.4L7.65 4.85q1.025-.425 2.1-.6375T12 4q3.775 0 6.725 2.0875T23 11.5q-.575 1.475-1.5125 2.7375T19.3 16.45Zm.5 6.15-4.2-4.15q-.875.275-1.7625.4125T12 19q-3.775 0-6.725-2.0875T1 11.5q.525-1.325 1.325-2.4625T4.15 7L1.4 4.2 2.8 2.8 21.2 21.2l-1.4 1.4ZM5.55 8.4q-.725.65-1.325 1.425T3.2 11.5q1.25 2.525 3.5875 4.0125T12 17q.5 0 .975-.0625T13.95 16.8l-.9-.95q-.275.075-.525.1125T12 16q-1.875 0-3.1875-1.3125T7.5 11.5q0-.275.0375-.525T7.65 10.45L5.55 8.4Zm7.975 2.325ZM9.75 12.6Z"), + VOLUNTEER_ACTIVISM_OUTLINE("m16 13l-4.15-4.05q-.775-.75-1.312-1.662T10 5.3q0-1.375.963-2.337T13.3 2q.8 0 1.5.338t1.2.912q.5-.575 1.2-.913T18.7 2q1.375 0 2.338.963T22 5.3q0 1.075-.525 1.988t-1.3 1.662zm0-2.8l2.725-2.675q.475-.475.875-1.013T20 5.3q0-.55-.375-.925T18.7 4q-.35 0-.663.137t-.537.413L16 6.35l-1.5-1.8q-.225-.275-.537-.413T13.3 4q-.55 0-.925.375T12 5.3q0 .675.4 1.213t.875 1.012zm-9 8.3l6.95 1.9l5.95-1.85q-.125-.225-.363-.388T19 18h-5.05q-.675 0-1.075-.05t-.825-.2l-2.325-.775l.55-1.95l2.025.675q.425.125 1 .2t1.7.1q0-.275-.162-.525t-.388-.325L8.6 13H7zM1 22V11h7.6q.175 0 .35.038t.325.087L15.15 13.3q.825.3 1.338 1.05T17 16h2q1.25 0 2.125.825T22 19v1l-8 2.5l-7-1.95V22zm2-2h2v-7H3zM16 6.35"), WARNING("M1 21 12 2 23 21H1ZM4.45 19H19.55L12 6 4.45 19ZM12 18Q12.425 18 12.7125 17.7125T13 17Q13 16.575 12.7125 16.2875T12 16Q11.575 16 11.2875 16.2875T11 17Q11 17.425 11.2875 17.7125T12 18ZM11 15H13V10H11V15ZM12 12.5Z"), WB_SUNNY("M11 4V1H13V4H11ZM11 23V20H13V23H11ZM20 13V11H23V13H20ZM1 13V11H4V13H1ZM18.7 6.7 17.3 5.3 19.05 3.5 20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3 6.7 18.7 4.95 20.5ZM19.05 20.5 17.3 18.7 18.7 17.3 20.5 19.05 19.05 20.5ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18Q9.5 18 7.75 16.25T6 12Q6 9.5 7.75 7.75T12 6Q14.5 6 16.25 7.75T18 12Q18 14.5 16.25 16.25T12 18ZM12 16Q13.675 16 14.8375 14.8375T16 12Q16 10.325 14.8375 9.1625T12 8Q10.325 8 9.1625 9.1625T8 12Q8 13.675 9.1625 14.8375T12 16ZM12 12Z"), WB_SUNNY_FILL("M11 4V1h2V4H11Zm0 19V20h2v3H11Zm9-10V11h3v2H20ZM1 13V11H4v2H1ZM18.7 6.7 17.3 5.3l1.75-1.8L20.5 4.95 18.7 6.7ZM4.95 20.5 3.5 19.05 5.3 17.3l1.4 1.4-1.75 1.8Zm14.1 0-1.75-1.8 1.4-1.4 1.8 1.75-1.45 1.45ZM5.3 6.7 3.5 4.95 4.95 3.5 6.7 5.3 5.3 6.7ZM12 18q-2.5 0-4.25-1.75T6 12 7.75 7.75 12 6t4.25 1.75T18 12t-1.75 4.25T12 18Z"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineRadioButton.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineRadioButton.java new file mode 100644 index 00000000000..bfbd649080b --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/LineRadioButton.java @@ -0,0 +1,50 @@ +/* + * 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.construct; + +import com.jfoenix.controls.JFXRadioButton; +import javafx.geometry.Insets; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import org.jackhuang.hmcl.ui.FXUtils; + +public final class LineRadioButton extends LineButtonBase { + private static final String DEFAULT_STYLE_CLASS = "line-radio-button"; + + private final JFXRadioButton radioButton; + + public LineRadioButton(ToggleGroup group) { + this.getStyleClass().add(DEFAULT_STYLE_CLASS); + + this.radioButton = new JFXRadioButton(); + this.radioButton.setToggleGroup(group); + FXUtils.setLimitHeight(radioButton, 30); + setNode(IDX_LEADING, radioButton); + container.setPadding(Insets.EMPTY); + } + + @Override + public void fire() { + radioButton.fire(); + super.fire(); + } + + public RadioButton getRadioButton() { + return radioButton; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LanguageSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LanguageSettingsPage.java new file mode 100644 index 00000000000..b70c551835b --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LanguageSettingsPage.java @@ -0,0 +1,97 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 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.main; + +import javafx.geometry.Insets; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.LineButton; +import org.jackhuang.hmcl.ui.construct.LineRadioButton; +import org.jackhuang.hmcl.util.i18n.I18n; +import org.jackhuang.hmcl.util.i18n.SupportedLocale; + +import static org.jackhuang.hmcl.setting.SettingsManager.settings; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class LanguageSettingsPage extends ScrollPane { + public LanguageSettingsPage() { + this.setFitToWidth(true); + + VBox rootPane = new VBox(10); + rootPane.setPadding(new Insets(10)); + this.setContent(rootPane); + FXUtils.smoothScrolling(this); + + { + ComponentList contributionPaneList = new ComponentList(); + + { + LineButton contributionButton = LineButton.createExternalLinkButton(Metadata.LOCALIZATION_URL); + contributionButton.setTitle(i18n("settings.launcher.language.contribution.title")); + contributionButton.setSubtitle(i18n("settings.launcher.language.contribution.subtitle")); + contributionButton.setLeading(SVG.VOLUNTEER_ACTIVISM_OUTLINE, 28); + + contributionPaneList.getContent().addAll(contributionButton); + } + + rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language.contribution")), contributionPaneList); + } + + { + ComponentList languagePaneList = new ComponentList(); + + var toggleGroup = new ToggleGroup(); + var currentLocale = I18n.getLocale(); + { + for (var locale : SupportedLocale.getSupportedLocales()) { + LineRadioButton lineRadioButton = new LineRadioButton(toggleGroup); + + var completeness = locale.getTranslationCompleteness() != 0.0 ? String.format("%d%%", (int) Math.ceil(locale.getTranslationCompleteness() * 100)) : ""; + + if (locale.isSameLanguage(currentLocale)) { + lineRadioButton.setSubtitle(i18n("settings.launcher.language.completion", completeness)); + } else { + lineRadioButton.setSubtitle(locale.getDisplayName(currentLocale) + " / " + i18n("settings.launcher.language.completion", completeness)); + } + + lineRadioButton.getRadioButton().setUserData(locale); + + if (locale.equals(currentLocale)) { + toggleGroup.selectToggle(lineRadioButton.getRadioButton()); + } + + lineRadioButton.setTitle(locale.getDisplayName(locale)); + languagePaneList.getContent().addAll(lineRadioButton); + } + toggleGroup.selectedToggleProperty().map(Toggle::getUserData).addListener( + (observable, oldValue, newValue) -> { + settings().languageProperty().set((SupportedLocale) newValue); + } + ); + } + + rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 0de3963470d..13739bbcd38 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -20,8 +20,8 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import org.jackhuang.hmcl.game.HMCLGameRepository; -import org.jackhuang.hmcl.setting.GameSettings; import org.jackhuang.hmcl.setting.GameDirectoryManager; +import org.jackhuang.hmcl.setting.GameSettings; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.TransitionPane; @@ -40,6 +40,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor private final TabHeader.Tab> gameTab = new TabHeader.Tab<>("versionSettingsPage"); private final TabControl.Tab javaManagementTab = new TabControl.Tab<>("javaManagementPage"); private final TabHeader.Tab settingsTab = new TabHeader.Tab<>("settingsPage"); + private final TabHeader.Tab languageTab = new TabHeader.Tab<>("languagePage"); private final TabHeader.Tab personalizationTab = new TabHeader.Tab<>("personalizationPage"); private final TabHeader.Tab downloadTab = new TabHeader.Tab<>("downloadSettingsPage"); private final TabHeader.Tab helpTab = new TabHeader.Tab<>("helpPage"); @@ -51,12 +52,13 @@ public LauncherSettingsPage() { gameTab.setNodeSupplier(() -> new GameSettingsPage<>(GameSettings.Preset.class)); javaManagementTab.setNodeSupplier(JavaManagementPage::new); settingsTab.setNodeSupplier(SettingsPage::new); + languageTab.setNodeSupplier(LanguageSettingsPage::new); personalizationTab.setNodeSupplier(PersonalizationPage::new); downloadTab.setNodeSupplier(DownloadSettingsPage::new); helpTab.setNodeSupplier(HelpPage::new); feedbackTab.setNodeSupplier(FeedbackPage::new); aboutTab.setNodeSupplier(AboutPage::new); - tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab); + tab = new TabHeader(transitionPane, gameTab, javaManagementTab, settingsTab, languageTab, personalizationTab, downloadTab, helpTab, feedbackTab, aboutTab); tab.select(gameTab); addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> gameTab.getNode().loadInstance(GameDirectoryManager.getSelectedRepository(), null)); @@ -66,6 +68,7 @@ public LauncherSettingsPage() { .addNavigationDrawerTab(tab, javaManagementTab, i18n("java.management"), SVG.LOCAL_CAFE, SVG.LOCAL_CAFE_FILL) .startCategory(i18n("launcher").toUpperCase(Locale.ROOT)) .addNavigationDrawerTab(tab, settingsTab, i18n("settings.launcher.general"), SVG.TUNE) + .addNavigationDrawerTab(tab, languageTab, i18n("settings.launcher.language"), SVG.TRANSLATE) .addNavigationDrawerTab(tab, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE, SVG.STYLE_FILL) .addNavigationDrawerTab(tab, downloadTab, i18n("download"), SVG.DOWNLOAD) .startCategory(i18n("help").toUpperCase(Locale.ROOT)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index 8c5961d4487..d188574f1d7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -43,8 +43,6 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.i18n.I18n; -import org.jackhuang.hmcl.util.i18n.SupportedLocale; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.tukaani.xz.XZInputStream; @@ -164,40 +162,6 @@ protected int getTrailingTextIndex() { rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("update")), updatePaneList); } - { - ComponentList languagePaneList = new ComponentList(); - - { - var chooseLanguagePane = new LineSelectButton(); - chooseLanguagePane.setTitle(i18n("settings.launcher.language")); - chooseLanguagePane.setSubtitle(i18n("settings.take_effect_after_restart")); - - SupportedLocale currentLocale = I18n.getLocale(); - chooseLanguagePane.setNullSafeConverter(it -> it.getDisplayName(currentLocale)); - chooseLanguagePane.setNullSafeDescriptionConverter(locale -> { - if (locale.isDefault()) - return ""; - - String name = locale.getDisplayName(locale); - - double completeness = locale.getTranslationCompleteness(); - if (completeness != 0.0) - return String.format("%s - %.2f%%", name, completeness * 100); - else return name; - }); - chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales()); - chooseLanguagePane.valueProperty().bindBidirectional(settings().languageProperty()); - - LineButton helpButton = LineButton.createExternalLinkButton(Metadata.LOCALIZATION_URL); - helpButton.setTitle(i18n("settings.launcher.language.contribution.title")); - helpButton.setSubtitle(i18n("settings.launcher.language.contribution.subtitle")); - - languagePaneList.getContent().addAll(chooseLanguagePane, helpButton); - } - - rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.language")), languagePaneList); - } - { ComponentList miscPaneList = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java index 010f2065a29..b2d156d0ebb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/SupportedLocale.java @@ -313,9 +313,9 @@ public Translator getTranslator() { public double getTranslationCompleteness() { if (METHOOD_LOOKUP_HANDLE_KEY_SET == null) return 0.0; - ResourceBundle enBundle = SupportedLocale.getLocale(Locale.ENGLISH).getResourceBundle(); - Set enKeys = enBundle.keySet(); - int totalKeys = enKeys.size(); + ResourceBundle enBundle = SupportedLocale.getLocale(Locale.SIMPLIFIED_CHINESE).getResourceBundle(); + Set baseKeys = enBundle.keySet(); + int totalKeys = baseKeys.size(); if (totalKeys == 0) return 0.0; ResourceBundle currentBundle = getResourceBundle(); @@ -331,7 +331,7 @@ public double getTranslationCompleteness() { } long actualTranslatedCount = handleKeys.stream() - .filter(enKeys::contains) + .filter(baseKeys::contains) .count(); double v = (double) actualTranslatedCount / totalKeys; @@ -343,6 +343,17 @@ public boolean isSameLanguage(SupportedLocale other) { .equals(LocaleUtils.getRootLanguage(other.getLocale())); } + @Override + public String toString() { + return "SupportedLocale{" + + "isDefault=" + isDefault + + ", name='" + name + '\'' + + ", locale=" + locale + + ", displayLocale=" + displayLocale + + ", textDirection=" + textDirection + + '}'; + } + public static final class TypeAdapter extends com.google.gson.TypeAdapter { @Override public void write(JsonWriter out, SupportedLocale value) throws IOException { diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 6b1cf2c8920..146fb6ed44f 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -456,12 +456,12 @@ -fx-min-width: 200px; -fx-border-width: 0 3px 0 0; -fx-border-color: linear-gradient( - to bottom, - transparent 0%, - transparent 20%, + to bottom, + transparent 0%, + transparent 20%, -monet-on-surface-variant 20%, -monet-on-surface-variant 80%, - transparent 80%, + transparent 80%, transparent 100% ); -fx-background-radius: 4px 0 0 4px; @@ -2014,6 +2014,10 @@ -fx-text-fill: -monet-tertiary; } +.line-radio-button .line-component-container { + -fx-padding: 10 16 10 4; +} + /******************************************************************************* * * * Mod Changelog * diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ca60b1b0a01..9acac52110a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1628,6 +1628,10 @@ settings.launcher.font.anti_aliasing.lcd=Sub-pixel settings.launcher.fonts=Fonts settings.launcher.general=General settings.launcher.language=Language +settings.launcher.language.completion=Completion: %s +settings.launcher.language.contribution=Contribute +settings.launcher.language.contribution.title=Your language not available or translation quality poor? +settings.launcher.language.contribution.subtitle=Click here to learn how to contribute translations to us settings.launcher.launcher_log.export=Export Launcher Logs settings.launcher.launcher_log.export.failed=Failed to export logs. settings.launcher.launcher_log.export.success=Logs have been exported to "%s". diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index 05b429a1074..8b156bf55da 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -1453,6 +1453,10 @@ settings.launcher.font.anti_aliasing.gray=تدرج رمادي settings.launcher.font.anti_aliasing.lcd=بكسل فرعي settings.launcher.general=عام settings.launcher.language=اللغة +settings.launcher.language.completion=اكتمال: %s +settings.launcher.language.contribution=المساهمة +settings.launcher.language.contribution.title=لغتك غير متوفرة أو جودة الترجمة ضعيفة؟ +settings.launcher.language.contribution.subtitle=انقر هنا لمعرفة كيفية المساهمة في الترجمة لدينا settings.launcher.launcher_log.export=تصدير سجلات المشغّل settings.launcher.launcher_log.export.failed=فشل تصدير السجلات. settings.launcher.launcher_log.export.success=تم تصدير السجلات إلى "%s". diff --git a/HMCL/src/main/resources/assets/lang/I18N_de.properties b/HMCL/src/main/resources/assets/lang/I18N_de.properties index c27c6b41abb..e41a930cdd2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_de.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_de.properties @@ -1627,6 +1627,10 @@ settings.launcher.font.anti_aliasing.lcd=Sub-Pixel settings.launcher.fonts=Schriftarten settings.launcher.general=Allgemein settings.launcher.language=Sprache +settings.launcher.language.completion=Vollständigkeit: %s +settings.launcher.language.contribution=Beitragen +settings.launcher.language.contribution.title=Ihre Sprache nicht verfügbar oder Übersetzungsqualität mangelhaft? +settings.launcher.language.contribution.subtitle=Klicken Sie hier, um zu erfahren, wie Sie Übersetzungen zu uns beitragen können settings.launcher.launcher_log.export=Launcher-Protokolle exportieren settings.launcher.launcher_log.export.failed=Fehler beim Exportieren der Protokolle. settings.launcher.launcher_log.export.success=Protokolle wurden nach "%s" exportiert. diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index c1ab2c80d98..db55885e42b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -1341,6 +1341,10 @@ settings.launcher.font.anti_aliasing.gray=Escala de grises settings.launcher.font.anti_aliasing.lcd=Subpíxel settings.launcher.general=General settings.launcher.language=Idioma +settings.launcher.language.completion=Completitud: %s +settings.launcher.language.contribution=Contribuir +settings.launcher.language.contribution.title=¿Su idioma no está disponible o la calidad de la traducción es deficiente? +settings.launcher.language.contribution.subtitle=Haga clic aquí para saber cómo contribuir con traducciones settings.launcher.launcher_log.export=Exportar registros del launcher settings.launcher.launcher_log.export.failed=No se han podido exportar los registros settings.launcher.launcher_log.export.success=Los registros se han exportado a «%s» diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index a7a0ff236df..0f3087db2f6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -871,6 +871,10 @@ settings.launcher.enable_game_list=メインページにバージョンリスト settings.launcher.font=フォント settings.launcher.general=全般的 settings.launcher.language=言語 +settings.launcher.language.completion=完了度: %s +settings.launcher.language.contribution=貢献する +settings.launcher.language.contribution.title=お使いの言語が利用できないか、翻訳の品質が低いですか? +settings.launcher.language.contribution.subtitle=ここをクリックして、翻訳に貢献する方法をご確認ください settings.launcher.launcher_log.export=ランチャーログのエクスポート settings.launcher.launcher_log.export.failed=ログのエクスポートに失敗しました settings.launcher.launcher_log.export.success=ログが %s にエクスポートされました diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index fef9114fe92..83d819f1330 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -1176,6 +1176,10 @@ settings.launcher.font.anti_aliasing.gray=灰階 settings.launcher.font.anti_aliasing.lcd=子像素 settings.launcher.general=貫用 settings.launcher.language=文 +settings.launcher.language.completion=完備之度: %s +settings.launcher.language.contribution=助譯 +settings.launcher.language.contribution.title=君所用之語未備,或譯文不精乎? +settings.launcher.language.contribution.subtitle=點此以觀如何助吾輩譯事 settings.launcher.launcher_log.export=錄出啟者之誌 settings.launcher.launcher_log.export.failed=錄誌出而未成 settings.launcher.launcher_log.export.success=誌既存於「%s」 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 9c603092dfe..6b37ece1585 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -1330,6 +1330,10 @@ settings.launcher.font.anti_aliasing.gray=Оттенки серого settings.launcher.font.anti_aliasing.lcd=Субпиксель settings.launcher.general=Общие settings.launcher.language=Язык +settings.launcher.language.completion=Полнота: %s +settings.launcher.language.contribution=Внести вклад +settings.launcher.language.contribution.title=Ваш язык недоступен или качество перевода низкое? +settings.launcher.language.contribution.subtitle=Нажмите здесь, чтобы узнать, как помочь нам с переводами settings.launcher.launcher_log.export=Экспорт логов лаунчера settings.launcher.launcher_log.export.failed=Не удалось экспортировать логи settings.launcher.launcher_log.export.success=Логи экспортированы в %s diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index d26af5edb24..cbf96440846 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -1573,6 +1573,10 @@ settings.launcher.font.anti_aliasing.lcd=Субпіксельне settings.launcher.fonts=Шрифти settings.launcher.general=Загальні settings.launcher.language=Мова +settings.launcher.language.completion=Повнота: %s +settings.launcher.language.contribution=Зробити внесок +settings.launcher.language.contribution.title=Ваша мова недоступна або якість перекладу низька? +settings.launcher.language.contribution.subtitle=Натисніть тут, щоб дізнатися, як допомогти нам з перекладами settings.launcher.launcher_log.export=Експортувати журнали лаунчера settings.launcher.launcher_log.export.failed=Не вдалося експортувати журнали. settings.launcher.launcher_log.export.success=Журнали було експортовано до "%s". diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 5eeafafcb3b..7a854636066 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1429,6 +1429,10 @@ settings.launcher.font.anti_aliasing.lcd=子像素 settings.launcher.fonts=字體 settings.launcher.general=一般 settings.launcher.language=語言 +settings.launcher.language.completion=完整程度: %s +settings.launcher.language.contribution=貢獻 +settings.launcher.language.contribution.title=沒有你使用的語言或翻譯品質欠佳? +settings.launcher.language.contribution.subtitle=點擊此處了解如何向我們貢獻翻譯 settings.launcher.launcher_log.export=匯出啟動器日誌 settings.launcher.launcher_log.export.failed=無法匯出日誌。 settings.launcher.launcher_log.export.success=日誌已儲存到「%s」。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 6fe08134e54..d711ffd04d4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1429,6 +1429,10 @@ settings.launcher.font.anti_aliasing.lcd=子像素 settings.launcher.fonts=字体 settings.launcher.general=通用 settings.launcher.language=语言 +settings.launcher.language.completion=完整程度: %s +settings.launcher.language.contribution=贡献 +settings.launcher.language.contribution.title=没有你使用的语言或翻译质量欠佳? +settings.launcher.language.contribution.subtitle=点击此处了解如何向我们贡献翻译 settings.launcher.launcher_log.export=导出启动器日志 settings.launcher.launcher_log.export.failed=无法导出日志 settings.launcher.launcher_log.export.success=日志已保存到“%s” From f160fc34da472d4641e3874c9a7218080fb6f49b Mon Sep 17 00:00:00 2001 From: CiiLu <109708109+CiiLu@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:48:06 +0800 Subject: [PATCH 3/3] qwq --- .../java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java index 839ad493642..873444fd221 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/LauncherSettingsPage.java @@ -19,6 +19,7 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; +import org.jackhuang.hmcl.game.HMCLGameInstance; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.setting.GameDirectoryManager; import org.jackhuang.hmcl.setting.GameSettings;