feat: WIP: FeedbackPage

This commit is contained in:
huanghongxun
2021-09-08 22:23:03 +08:00
parent 279b2d6f80
commit e385a85755
8 changed files with 517 additions and 9 deletions

View File

@@ -444,4 +444,34 @@ public final class SVG {
"M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z", "M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z",
fill, width, height); fill, width, height);
} }
public static Node messageAlertOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath(
"M13,10H11V6H13V10M13,12H11V14H13V12M22,4V16A2,2 0 0,1 20,18H6L2,22V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4M20,4H4V17.2L5.2,16H20V4Z",
fill, width, height);
}
public static Node checkCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath(
"M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z",
fill, width, height);
}
public static Node closeCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath(
"M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z",
fill, width, height);
}
public static Node clockOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath(
"M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z",
fill, width, height);
}
public static Node magnify(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath(
"M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z",
fill, width, height);
}
} }

View File

@@ -67,14 +67,13 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
private boolean showMethodSwitcher; private boolean showMethodSwitcher;
private AccountFactory<?> factory; private AccountFactory<?> factory;
private Label lblErrorMessage; private final Label lblErrorMessage;
private JFXButton btnAccept; private final JFXButton btnAccept;
private SpinnerPane spinner; private final SpinnerPane spinner;
private JFXButton btnCancel; private final Node body;
private Node body;
private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft
private Pane detailsContainer; private final Pane detailsContainer;
private TaskExecutor loginTask; private TaskExecutor loginTask;
@@ -109,7 +108,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
{ {
lblErrorMessage = new Label(); lblErrorMessage = new Label();
btnAccept = new JFXButton(i18n("button.ok")); btnAccept = new JFXButton(i18n("account.login"));
btnAccept.getStyleClass().add("dialog-accept"); btnAccept.getStyleClass().add("dialog-accept");
btnAccept.setOnAction(e -> onAccept()); btnAccept.setOnAction(e -> onAccept());
@@ -117,7 +116,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
spinner.getStyleClass().add("small-spinner-pane"); spinner.getStyleClass().add("small-spinner-pane");
spinner.setContent(btnAccept); spinner.setContent(btnAccept);
btnCancel = new JFXButton(i18n("button.cancel")); JFXButton btnCancel = new JFXButton(i18n("button.cancel"));
btnCancel.getStyleClass().add("dialog-cancel"); btnCancel.getStyleClass().add("dialog-cancel");
btnCancel.setOnAction(e -> onCancel()); btnCancel.setOnAction(e -> onCancel());
onEscPressed(this, btnCancel::fire); onEscPressed(this, btnCancel::fire);

View File

@@ -0,0 +1,375 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.main;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class FeedbackPage extends VBox {
private final ObjectProperty<HMCLAccount> account = new SimpleObjectProperty<>();
private final ObservableList<FeedbackResponse> feedbacks = FXCollections.observableArrayList();
private final SpinnerPane spinnerPane = new SpinnerPane();
public FeedbackPage() {
setSpacing(10);
setPadding(new Insets(10));
{
HBox loginPane = new HBox(16);
loginPane.getStyleClass().add("card");
TwoLineListItem accountInfo = new TwoLineListItem();
accountInfo.titleProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.not_logged_in") : account.getNickname()));
accountInfo.subtitleProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.not_logged_in") : account.getEmail()));
JFXButton logButton = new JFXButton();
logButton.textProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.login") : i18n("account.logout")));
logButton.setOnAction(e -> log());
loginPane.getChildren().setAll(accountInfo, logButton);
getChildren().add(loginPane);
}
{
HBox searchPane = new HBox(8);
searchPane.getStyleClass().add("card");
getChildren().add(searchPane);
JFXTextField searchField = new JFXTextField();
searchField.setOnAction(e -> search(searchField.getText()));
HBox.setHgrow(searchField, Priority.ALWAYS);
searchField.setPromptText(i18n("search"));
JFXButton searchButton = new JFXButton();
searchButton.getStyleClass().add("toggle-icon4");
searchButton.setGraphic(SVG.magnify(Theme.blackFillBinding(), -1, -1));
searchButton.setOnAction(e -> addFeedback());
searchPane.getChildren().setAll(searchField, searchButton);
}
{
spinnerPane.getStyleClass().add("card");
VBox.setVgrow(spinnerPane, Priority.ALWAYS);
JFXListView<FeedbackResponse> listView = new JFXListView<>();
spinnerPane.setContent(listView);
Bindings.bindContent(listView.getItems(), feedbacks);
getChildren().add(spinnerPane);
}
}
private void search(String keyword) {
Task.supplyAsync(() -> {
return HttpRequest.GET("https://hmcl.huangyuhui.net/api/feedback", pair("s", keyword)).<List<FeedbackResponse>>getJson(new TypeToken<List<FeedbackResponse>>(){}.getType());
}).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
spinnerPane.hideSpinner();
if (exception != null) {
spinnerPane.setFailedReason(i18n("feedback.failed"));
} else {
feedbacks.setAll(result);
}
}).start();
}
private void log() {
if (account.get() == null) {
// login
Controllers.dialog(new LoginDialog());
} else {
// logout
account.set(null);
}
}
private void addFeedback() {
if (account.get() == null) {
Controllers.dialog(i18n("feedback.add.login"));
return;
}
Controllers.dialog(new AddFeedbackDialog());
}
private static class HMCLAccount {
private final String nickname;
private final String email;
private final String accessToken;
public HMCLAccount(String nickname, String email, String accessToken) {
this.nickname = nickname;
this.email = email;
this.accessToken = accessToken;
}
public String getNickname() {
return nickname;
}
public String getEmail() {
return email;
}
public String getAccessToken() {
return accessToken;
}
}
private static class HMCLLoginResponse {
private final int err;
private final String nickname;
private final String email;
private final String accessToken;
public HMCLLoginResponse(int err, String nickname, String email, String accessToken) {
this.err = err;
this.nickname = nickname;
this.email = email;
this.accessToken = accessToken;
}
public int getErr() {
return err;
}
public String getNickname() {
return nickname;
}
public String getEmail() {
return email;
}
public String getAccessToken() {
return accessToken;
}
public static final int ERR_OK = 0;
public static final int ERR_WRONG = 400;
}
private class LoginDialog extends JFXDialogLayout {
private final SpinnerPane spinnerPane = new SpinnerPane();
private final Label errorLabel = new Label();
public LoginDialog() {
setHeading(new Label(i18n("feedback.login")));
GridPane body = new GridPane();
ColumnConstraints fieldColumn = new ColumnConstraints();
fieldColumn.setFillWidth(true);
body.getColumnConstraints().setAll(new ColumnConstraints(), fieldColumn);
body.setVgap(8);
body.setHgap(8);
setBody(body);
JFXTextField usernameField = new JFXTextField();
usernameField.setValidators(new RequiredValidator());
body.addRow(0, new Label(i18n("account.username")), usernameField);
JFXPasswordField passwordField = new JFXPasswordField();
passwordField.setValidators(new RequiredValidator());
body.addRow(1, new Label(i18n("account.password")), passwordField);
JFXButton registerButton = new JFXButton();
registerButton.setText(i18n("account.register"));
registerButton.setOnAction(e -> FXUtils.openLink("https://hmcl.huangyuhui.net/user/login"));
JFXButton loginButton = new JFXButton();
spinnerPane.setContent(loginButton);
loginButton.setText(i18n("account.login"));
loginButton.setOnAction(e -> login(usernameField.getText(), passwordField.getText()));
JFXButton cancelButton = new JFXButton();
cancelButton.setText(i18n("button.cancel"));
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
onEscPressed(this, cancelButton::fire);
setActions(errorLabel, registerButton, spinnerPane, cancelButton);
}
private void login(String username, String password) {
spinnerPane.showSpinner();
errorLabel.setText("");
Task.supplyAsync(() -> {
return HttpRequest.POST("https://hmcl.huangyuhui.net/api/user/login")
.json(mapOf(
pair("username", username),
pair("password", password)
)).getJson(HMCLLoginResponse.class);
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null) {
if (exception instanceof IOException) {
if (exception instanceof ResponseCodeException && ((ResponseCodeException) exception).getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST) {
errorLabel.setText(i18n("account.failed.invalid_password"));
} else {
errorLabel.setText(i18n("account.failed.connect_authentication_server"));
}
} else if (exception instanceof JsonParseException) {
errorLabel.setText(i18n("account.failed.server_response_malformed"));
} else {
errorLabel.setText(exception.getClass().getName() + ": " + exception.getLocalizedMessage());
}
} else {
if (result.err == HMCLLoginResponse.ERR_OK) {
account.setValue(new HMCLAccount(result.getNickname(), result.getEmail(), result.getAccessToken()));
fireEvent(new DialogCloseEvent());
} else if (result.err == HMCLLoginResponse.ERR_WRONG) {
errorLabel.setText(i18n("account.failed.invalid_password"));
} else {
errorLabel.setText(i18n("account.failed", result.err));
}
}
}).start();
}
}
private static class AddFeedbackDialog extends JFXDialogLayout {
public AddFeedbackDialog() {
setHeading(new Label(i18n("feedback.add")));
GridPane body = new GridPane();
body.setVgap(8);
body.setHgap(8);
HintPane searchHintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
GridPane.setColumnSpan(searchHintPane, 2);
searchHintPane.setText(i18n("feedback.add.hint.search_before_add"));
body.addRow(0, searchHintPane);
HintPane titleHintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION);
GridPane.setColumnSpan(titleHintPane, 2);
titleHintPane.setText(i18n("feedback.add.hint.title"));
body.addRow(1, titleHintPane);
JFXTextField titleField = new JFXTextField();
titleField.setValidators(new RequiredValidator());
body.addRow(2, new Label(i18n("feedback.title")), titleField);
JFXComboBox<FeedbackType> comboBox = new JFXComboBox<>();
comboBox.setMaxWidth(-1);
comboBox.getItems().setAll(FeedbackType.values());
comboBox.getSelectionModel().select(0);
comboBox.setConverter(stringConverter(e -> i18n("feedback.type." + e.name().toLowerCase())));
body.addRow(3, new Label(i18n("feedback.type")), comboBox);
Label contentLabel = new Label(i18n("feedback.content"));
GridPane.setColumnSpan(contentLabel, 2);
body.addRow(4, contentLabel);
JFXTextArea contentArea = new JFXTextArea();
contentArea.setValidators(new RequiredValidator());
contentArea.setPromptText(i18n("feedback.add.hint.content"));
GridPane.setColumnSpan(contentArea, 2);
body.addRow(5, contentArea);
setBody(body);
JFXButton okButton = new JFXButton();
okButton.setText(i18n("button.ok"));
okButton.setOnAction(e -> addFeedback(titleField.getText(), comboBox.getSelectionModel().getSelectedItem(), contentArea.getText()));
JFXButton cancelButton = new JFXButton();
cancelButton.setText(i18n("button.cancel"));
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
onEscPressed(this, cancelButton::fire);
setActions(okButton, cancelButton);
}
private void addFeedback(String title, FeedbackType feedbackType, String content) {
// TODO
}
}
private static class FeedbackResponse {
private final int id;
private final String title;
private final String content;
private final String launcherVersion;
private final String gameVersion;
private final FeedbackType type;
public FeedbackResponse(int id, String title, String content, String launcherVersion, String gameVersion, FeedbackType type) {
this.id = id;
this.title = title;
this.content = content;
this.launcherVersion = launcherVersion;
this.gameVersion = gameVersion;
this.type = type;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
public String getLauncherVersion() {
return launcherVersion;
}
public String getGameVersion() {
return gameVersion;
}
public FeedbackType getType() {
return type;
}
}
private enum FeedbackType {
FEATURE_REQUEST,
BUG_REPORT
}
}

View File

@@ -44,6 +44,7 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
private final TabHeader.Tab<DownloadSettingsPage> downloadTab = new TabHeader.Tab<>("downloadSettingsPage"); private final TabHeader.Tab<DownloadSettingsPage> downloadTab = new TabHeader.Tab<>("downloadSettingsPage");
private final TabHeader.Tab<HelpPage> helpTab = new TabHeader.Tab<>("helpPage"); private final TabHeader.Tab<HelpPage> helpTab = new TabHeader.Tab<>("helpPage");
private final TabHeader.Tab<AboutPage> aboutTab = new TabHeader.Tab<>("aboutPage"); private final TabHeader.Tab<AboutPage> aboutTab = new TabHeader.Tab<>("aboutPage");
private final TabHeader.Tab<FeedbackPage> feedbackTab = new TabHeader.Tab<>("feedbackPage");
private final TabHeader.Tab<SponsorPage> sponsorTab = new TabHeader.Tab<>("sponsorPage"); private final TabHeader.Tab<SponsorPage> sponsorTab = new TabHeader.Tab<>("sponsorPage");
private final TransitionPane transitionPane = new TransitionPane(); private final TransitionPane transitionPane = new TransitionPane();
@@ -53,9 +54,10 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
personalizationTab.setNodeSupplier(PersonalizationPage::new); personalizationTab.setNodeSupplier(PersonalizationPage::new);
downloadTab.setNodeSupplier(DownloadSettingsPage::new); downloadTab.setNodeSupplier(DownloadSettingsPage::new);
helpTab.setNodeSupplier(HelpPage::new); helpTab.setNodeSupplier(HelpPage::new);
feedbackTab.setNodeSupplier(FeedbackPage::new);
sponsorTab.setNodeSupplier(SponsorPage::new); sponsorTab.setNodeSupplier(SponsorPage::new);
aboutTab.setNodeSupplier(AboutPage::new); aboutTab.setNodeSupplier(AboutPage::new);
tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, sponsorTab, aboutTab); tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, sponsorTab, aboutTab);
tab.getSelectionModel().select(gameTab); tab.getSelectionModel().select(gameTab);
gameTab.initializeIfNeeded(); gameTab.initializeIfNeeded();
@@ -99,6 +101,12 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab)); helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab)); helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab));
}) })
.addNavigationDrawerItem(feedbackItem -> {
feedbackItem.setTitle(i18n("feedback"));
feedbackItem.setLeftGraphic(wrap(SVG.messageAlertOutline(Theme.blackFillBinding(), 24, 24)));
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
feedbackItem.setOnAction(e -> tab.getSelectionModel().select(feedbackTab));
})
.addNavigationDrawerItem(sponsorItem -> { .addNavigationDrawerItem(sponsorItem -> {
sponsorItem.setTitle(i18n("sponsor")); sponsorItem.setTitle(i18n("sponsor"));
sponsorItem.setLeftGraphic(wrap(SVG.handHearOutline(Theme.blackFillBinding(), 24, 24))); sponsorItem.setLeftGraphic(wrap(SVG.handHearOutline(Theme.blackFillBinding(), 24, 24)));

View File

@@ -43,6 +43,7 @@ account.character=character
account.choose=Choose a character account.choose=Choose a character
account.create=Create a new account account.create=Create a new account
account.email=Email account.email=Email
account.failed=Error code: %d
account.failed.character_deleted=The character has been deleted. account.failed.character_deleted=The character has been deleted.
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your internet connection. account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your internet connection.
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct. account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
@@ -61,6 +62,10 @@ account.injector.link.register=Register
account.injector.server=Auth Server account.injector.server=Auth Server
account.injector.server_url=Server URL account.injector.server_url=Server URL
account.injector.server_name=Server Name account.injector.server_name=Server Name
account.login=Login
account.login.hint=We will not save your password.
account.logout=Logout
account.register=註冊
account.manage=Account List account.manage=Account List
account.methods=Login Type account.methods=Login Type
account.methods.authlib_injector=authlib-injector account.methods.authlib_injector=authlib-injector
@@ -77,6 +82,7 @@ account.methods.offline=Offline
account.methods.yggdrasil=Mojang account.methods.yggdrasil=Mojang
account.missing=No Account account.missing=No Account
account.missing.add=Click here to add account.missing.add=Click here to add
account.not_logged_in=Not logged in
account.password=Password account.password=Password
account.skin.file=Skin file account.skin.file=Skin file
account.skin.upload=Upload skin account.skin.upload=Upload skin
@@ -85,6 +91,7 @@ account.skin.invalid_skin=Unrecognized skin file
account.username=Name account.username=Name
archive.author=Authors archive.author=Authors
archive.date=Publish Date
archive.game_version=Game archive.game_version=Game
archive.name=Nickname archive.name=Nickname
archive.version=Version archive.version=Version
@@ -245,6 +252,29 @@ fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure
fatal.migration_requires_manual_reboot=The update is complete. Please reopen Hello Minecraft! Launcher. fatal.migration_requires_manual_reboot=The update is complete. Please reopen Hello Minecraft! Launcher.
fatal.apply_update_failure=We're sorry, Hello Minecraft! Launcher couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading Hello Minecraft! Launcher from %s.\nPlease consider reporting this issue to us. fatal.apply_update_failure=We're sorry, Hello Minecraft! Launcher couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading Hello Minecraft! Launcher from %s.\nPlease consider reporting this issue to us.
feedback=Feedback
feedback.add=Add Feedback
feedback.add.hint.search_before_add=Before adding new feedback, you should search the keyword to find out what you want to report has been already reported or not. If has, you can upvote it to increase its priorit
feedback.add.hint.title=The title of the feedback should be able to summarize your needs concisely. Titles such as "I have a problem", "I have an idea", "Game won't open", etc. that cannot let others see the general problem at a glance are not acceptable.
feedback.add.hint.content=The feedback content needs to express your needs completely and concisely. If you encounter a problem, you need to describe the recurrence path in detail, such as what button is clicked after opening the launcher, and what problem is triggered after what operation is done. If you want to add new features, you need to elaborate: why players need the feature, what problems the feature can solve, and how the feature can be implemented.
feedback.add.login=You must login/register HMCL account to gain feedback permission.
feedback.add.permission=You must gain feedback permission to add new feedback.
feedback.author=Author
feedback.content=Content
feedback.failed=Failed to load
feedback.like=Like
feedback.login=Log in to HMCL account
feedback.response=Response
feedback.state.accepted=Accepted
feedback.state.pending=Pending
feedback.state.rejected=Rejected
feedback.title=Title
feedback.type=Type
feedback.type.bug_report=Bug Report
feedback.type.feature_request=Feature Request
feedback.unlike=Unlike
feedback.version=Launcher Version
file=File file=File
folder.config=Configs folder.config=Configs

View File

@@ -36,6 +36,7 @@ account.character=角色
account.choose=請選擇角色 account.choose=請選擇角色
account.create=建立帳戶 account.create=建立帳戶
account.email=電子信箱 account.email=電子信箱
account.failed=錯誤碼: %d
account.failed.character_deleted=已刪除此角色 account.failed.character_deleted=已刪除此角色
account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題 account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題
account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障或網址輸入錯誤 account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障或網址輸入錯誤
@@ -54,6 +55,10 @@ account.injector.link.register=註冊
account.injector.server=認證伺服器 account.injector.server=認證伺服器
account.injector.server_url=伺服器位址 account.injector.server_url=伺服器位址
account.injector.server_name=伺服器名稱 account.injector.server_name=伺服器名稱
account.login=登錄
account.login.hint=我們不會保存你的密碼
account.logout=登出
account.register=註冊
account.manage=帳戶列表 account.manage=帳戶列表
account.methods=登入方式 account.methods=登入方式
account.methods.authlib_injector=authlib-injector 登入 account.methods.authlib_injector=authlib-injector 登入
@@ -71,6 +76,7 @@ account.methods.offline=離線模式
account.methods.yggdrasil=正版登入 account.methods.yggdrasil=正版登入
account.missing=沒有遊戲帳戶 account.missing=沒有遊戲帳戶
account.missing.add=按一下此處加入帳戶 account.missing.add=按一下此處加入帳戶
account.not_logged_in=未登錄
account.password=密碼 account.password=密碼
account.skin.file=皮膚圖片檔案 account.skin.file=皮膚圖片檔案
account.skin.upload=上傳皮膚 account.skin.upload=上傳皮膚
@@ -79,6 +85,7 @@ account.skin.invalid_skin=無法識別的皮膚文件
account.username=使用者名稱 account.username=使用者名稱
archive.author=作者 archive.author=作者
archive.date=發布日期
archive.game_version=遊戲版本 archive.game_version=遊戲版本
archive.name=名稱 archive.name=名稱
archive.version=版本 archive.version=版本
@@ -235,6 +242,28 @@ fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。 fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n請考慮向我們回報該問題。 fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n請考慮向我們回報該問題。
feedback=回饋
feedback.add=新增回饋
feedback.add.hint.search_before_add=添加回饋前,請先搜索已有回饋中是否已經有人提出過相關內容,如果有,你可以透過給對應回饋按讚來提升對應回饋的優先度。
feedback.add.hint.title=回饋標題需能簡練概括你的需求。"我有問題"、"我有一個想法"、"遊戲打不開" 等無法讓其他人一眼看出大致問題的標題是不被接受的。
feedback.add.hint.content=回饋內容需完整且簡練地表達你的需求。如果你遇到了問題,你需要詳細描述復現路徑,比如在打開啟動器後通過點擊什麼按鈕,做了什麼操作後觸發了什麼問題。如果你希望添加新功能,你需要闡述:為什麼玩家需要該功能,該功能能解決什麼問題,該功能可以怎麼實現。
feedback.add.login=你需要先登錄/註冊 HMCL 回饋帳號並獲得回饋權限才能添加回饋。
feedback.add.permission=你需要獲得回饋權限才能添加回饋。
feedback.author=發布者
feedback.content=正文
feedback.like=贊成
feedback.login=登錄 HMCL 帳號
feedback.response=回復
feedback.state.accepted=接受
feedback.state.pending=審核中
feedback.state.rejected=拒絕
feedback.title=標題
feedback.type=類型
feedback.type.bug_report=問題回饋
feedback.type.feature_request=新功能請求
feedback.unlike=反對
feedback.version=啟動器版本
file=檔案 file=檔案
folder.config=設定資料夾 folder.config=設定資料夾

View File

@@ -48,6 +48,7 @@ account.create.yggdrasil=添加 Mojang 账户
account.create.offline=添加离线模式账户 account.create.offline=添加离线模式账户
account.create.authlibInjector=添加外置登录账户 (authlib-injector) account.create.authlibInjector=添加外置登录账户 (authlib-injector)
account.email=邮箱 account.email=邮箱
account.failed=错误码: %d
account.failed.character_deleted=此角色已被删除 account.failed.character_deleted=此角色已被删除
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题 account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误 account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
@@ -64,6 +65,10 @@ account.injector.link.register=注册
account.injector.server=认证服务器 account.injector.server=认证服务器
account.injector.server_url=服务器地址 account.injector.server_url=服务器地址
account.injector.server_name=服务器名称 account.injector.server_name=服务器名称
account.login=登录
account.login.hint=我们不会保存你的密码
account.logout=登出
account.register=注册
account.manage=账户列表 account.manage=账户列表
account.methods=登录方式 account.methods=登录方式
account.methods.authlib_injector=外置登录 (authlib-injector) account.methods.authlib_injector=外置登录 (authlib-injector)
@@ -81,6 +86,7 @@ account.methods.offline=离线模式
account.methods.yggdrasil=Mojang 账号 account.methods.yggdrasil=Mojang 账号
account.missing=没有游戏账户 account.missing=没有游戏账户
account.missing.add=点击此处添加账户 account.missing.add=点击此处添加账户
account.not_logged_in=未登录
account.password=密码 account.password=密码
account.skin.file=皮肤图片文件 account.skin.file=皮肤图片文件
account.skin.upload=上传皮肤 account.skin.upload=上传皮肤
@@ -89,6 +95,7 @@ account.skin.invalid_skin=无法识别的皮肤文件
account.username=用户名 account.username=用户名
archive.author=作者 archive.author=作者
archive.date=发布日期
archive.game_version=游戏版本 archive.game_version=游戏版本
archive.name=名称 archive.name=名称
archive.version=版本 archive.version=版本
@@ -252,6 +259,29 @@ fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。 fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。
fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。\n请考虑向我们反馈该问题。 fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。\n请考虑向我们反馈该问题。
feedback=反馈
feedback.add=新增反馈
feedback.add.hint.search_before_add=添加反馈前,请先搜索已有反馈中是否已经有人提出过相关内容,如果有,你可以通过给对应反馈点赞来提升对应反馈的优先级。
feedback.add.hint.title=反馈标题需能简练概括你的需求。带有 "我有问题"、"我有一个想法"、"游戏打不开" 等无法让其他人一眼看出大致问题的标题的反馈将会被直接关闭。
feedback.add.hint.content=反馈内容需完整且简练地表达你的需求。如果你遇到了问题,你需要详细描述复现路径,比如在打开启动器后通过点击什么按钮,做了什么操作后触发了什么问题。如果你希望添加新功能,你需要阐述:为什么玩家需要该功能,该功能能解决什么问题,该功能可以怎么实现。
feedback.add.login=你需要先登录/注册 HMCL 反馈账号并获得反馈权限才能添加反馈。
feedback.add.permission=你需要获得反馈权限才能添加反馈。
feedback.author=发布者
feedback.content=正文
feedback.failed=載入失敗
feedback.like=赞成
feedback.login=登录 HMCL 账号
feedback.response=回复
feedback.state.accepted=接受
feedback.state.pending=审核中
feedback.state.rejected=拒绝
feedback.title=标题
feedback.type=类型
feedback.type.bug_report=问题反馈
feedback.type.feature_request=新功能请求
feedback.unlike=反对
feedback.version=启动器版本
file=文件 file=文件
folder.config=配置文件夹 folder.config=配置文件夹
@@ -261,6 +291,9 @@ folder.resourcepacks=资源包文件夹
folder.saves=存档文件夹 folder.saves=存档文件夹
folder.screenshots=截图文件夹 folder.screenshots=截图文件夹
game=游戏
game.version=游戏版本
help=帮助 help=帮助
help.doc=Hello Minecraft! Launcher 帮助文档 help.doc=Hello Minecraft! Launcher 帮助文档
help.detail=可查阅数据包、整合包制作指南等内容。 help.detail=可查阅数据包、整合包制作指南等内容。

View File

@@ -151,6 +151,10 @@ public abstract class HttpRequest {
if (responseCodeTester != null) { if (responseCodeTester != null) {
responseCodeTester.accept(new URL(url), con.getResponseCode()); responseCodeTester.accept(new URL(url), con.getResponseCode());
} else {
if (con.getResponseCode() / 100 != 2) {
throw new ResponseCodeException(new URL(url), con.getResponseCode());
}
} }
try (OutputStream os = con.getOutputStream()) { try (OutputStream os = con.getOutputStream()) {