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 bc703be99f6..357145c3147 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -58,6 +58,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/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/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/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 9493dd9fcec..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,10 +19,10 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; -import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.HMCLGameInstance; -import org.jackhuang.hmcl.setting.GameSettings; +import org.jackhuang.hmcl.game.HMCLGameRepository; 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; @@ -41,6 +41,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"); @@ -52,12 +53,13 @@ public LauncherSettingsPage() { gameTab.setNodeSupplier(() -> new GameSettingsPage<>(GameSettings.Preset.class, null)); 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(HMCLGameInstance.Optional.empty(GameDirectoryManager.getSelectedRepository()))); @@ -67,6 +69,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 ee8ea5593cf..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,33 +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(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); - }); - chooseLanguagePane.setItems(SupportedLocale.getSupportedLocales()); - chooseLanguagePane.valueProperty().bindBidirectional(settings().languageProperty()); - - languagePaneList.getContent().add(chooseLanguagePane); - - } - - 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 248e694be00..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 @@ -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,11 +295,65 @@ 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.SIMPLIFIED_CHINESE).getResourceBundle(); + Set baseKeys = enBundle.keySet(); + int totalKeys = baseKeys.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(baseKeys::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())); } + @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 1a5e3837b97..14fcb62c3e8 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -447,12 +447,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; @@ -1950,6 +1950,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 b06e2c413ef..56e72e9dde0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1562,6 +1562,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 95e20c24680..c47680e5ec5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -1387,6 +1387,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 997645d3b97..2e4147c2ecd 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_de.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_de.properties @@ -1558,6 +1558,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 ec11203a3d6..c293b7545f9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -1284,6 +1284,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 5e4fb6d3c35..2eeb453692c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -826,6 +826,10 @@ settings.launcher.download_source.auto=ダウンロードソースの自動選 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 13a3d35e687..dbb05426ac2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -1118,6 +1118,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 d2c83825f6d..9ad9006396d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -1275,6 +1275,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 9c8c0ba7ed4..e128d4f75fb 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -1529,6 +1529,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 3f95224821a..64e202eb01f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1368,6 +1368,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 c8c594a4440..a96e061349f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1373,6 +1373,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”