diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 94c97a8b79..2e59cccfa7 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -129,6 +129,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 bc703be99f..357145c314 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 9991ac13b8..61fe0071d0 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 0000000000..bfbd649080 --- /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 e80db45871..9859635f55 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 0000000000..b70c551835 --- /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 9493dd9fce..873444fd22 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 7745691953..520a70eb8f 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 @@ -40,8 +40,6 @@ import org.jackhuang.hmcl.upgrade.*; 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; @@ -170,33 +168,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 248e694be0..b2d156d0eb 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 099a1e624c..603490e513 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; @@ -1964,6 +1964,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 9a56febc90..a5ad5e475b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1569,6 +1569,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 635db716b6..e11fb31806 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -1567,6 +1567,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_de.properties b/HMCL/src/main/resources/assets/lang/I18N_de.properties index 0e3534cb79..cf2fbef515 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_de.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_de.properties @@ -1568,6 +1568,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 38d32da099..8cba45b491 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -1559,6 +1559,10 @@ settings.launcher.font.anti_aliasing.lcd=Subpíxel # settings.launcher.fonts= 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 d6f3e2fc5d..c714ebb83d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -1363,6 +1363,10 @@ settings.launcher.font=フォント # 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_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index 0abaf77bbf..1f44abb0a9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -1375,6 +1375,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_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 8b75ae658b..c7dc539ed1 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -1499,6 +1499,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_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index 6f1985bff0..0b126c6087 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -1522,6 +1522,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_Hans.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties index be22b30fc3..875330ba2b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hans.properties @@ -1381,6 +1381,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_Hant.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties index 458f32c556..3f15b526e7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_Hant.properties @@ -1375,6 +1375,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」。