diff --git a/messages.properties b/messages.properties new file mode 100644 index 0000000..88d2991 --- /dev/null +++ b/messages.properties @@ -0,0 +1,5 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template + + diff --git a/resources/messages.properties b/resources/messages.properties new file mode 100644 index 0000000..ba14972 --- /dev/null +++ b/resources/messages.properties @@ -0,0 +1,25 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template + +# Mainframe.java +mainframe.closing.animating=\u8bf7\u7b49\u5f85\u5f53\u524d\u79fb\u52a8\u52a8\u753b\u7ed3\u675f +mainframe.closing.confirm.save=\u662f\u5426\u4fdd\u5b58\u6e38\u620f\uff1f +mainframe.closing.confirm.exit=\u9000\u51fa\u6e38\u620f +dialog.loadaudio=\u8df3\u8fc7\u7f3a\u5931\u7684\u80cc\u666f\u97f3\u4e50: + +# AISolveCoordinator +aisolve.stop=\u505c\u6b62\u63a8\u6f14 + +label.username=\u7528\u6237\u540d +label.password=\u5bc6\u7801 +button.login=\u767b\u5f55 +button.register=\u6ce8\u518c +button.guest=\u6e38\u5ba2\u767b\u5f55 +dialog.emptyfield=\u7528\u6237\u540d\u6216\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a +dialog.syntax=\u7528\u6237\u540d\u53ea\u80fd\u5305\u542b\u6570\u5b57\u3001\u6c49\u5b57\u548c\u5927\u5c0f\u5199\u5b57\u6bcd\\n\u5bc6\u7801\u53ea\u80fd\u5305\u542b\u6570\u5b57\u548c\u5927\u5c0f\u5199\u5b57\u6bcd +dialog.invalid_login=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef +dialog.fail_login=\u8bfb\u53d6\u73a9\u5bb6\u6570\u636e\u5931\u8d25\uff1a +dialog.register_success=\u672c\u5730\u73a9\u5bb6\u521b\u5efa\u6210\u529f +dialog.register_taken=\u8be5\u7528\u6237\u540d\u5df2\u88ab\u6ce8\u518c +dialog.register_fail=\u4fdd\u5b58\u73a9\u5bb6\u6570\u636e\u5931\u8d25\uff1a diff --git a/resources/messages_en.properties b/resources/messages_en.properties new file mode 100644 index 0000000..d1e4973 --- /dev/null +++ b/resources/messages_en.properties @@ -0,0 +1,25 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template + + +# Mainframe.java +mainframe.closing.animating=Please wait for the current animation to finish. +mainframe.closing.confirm.save=Save +mainframe.closing.confirm.exit=Exit +dialog.loadaudio=Skipping missing background music: + +# AISolveCoordinator +aisolve.stop=\u505c\u6b62\u63a8\u6f14 +label.username=username +label.password=password +button.login=login +button.register=register +button.guest=guest login +dialog.emptyfield=Username or password cannot be empty. +dialog.syntax=Usernames may only contain numbers, Chinese characters, and uppercase and lowercase letters.\\nPasswords may only contain numbers and uppercase and lowercase letters. +dialog.invalid_login=Incorrect username or password +dialog.fail_login=Failed to load player data +dialog.register_success=Local player successfully created +dialog.register_taken=This username has already been registered +dialog.register_fail=Failed to save player data: diff --git a/resources/messages_zh_CN.properties b/resources/messages_zh_CN.properties new file mode 100644 index 0000000..40d5e7f --- /dev/null +++ b/resources/messages_zh_CN.properties @@ -0,0 +1,25 @@ + +# Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license +# Click nbfs://nbhost/SystemFileSystem/Templates/Other/properties.properties to edit this template + + +# Mainframe.java +mainframe.closing.animating=\u8bf7\u7b49\u5f85\u5f53\u524d\u79fb\u52a8\u52a8\u753b\u7ed3\u675f +mainframe.closing.confirm.save=\u662f\u5426\u4fdd\u5b58\u6e38\u620f\uff1f +mainframe.closing.confirm.exit=\u9000\u51fa\u6e38\u620f +dialog.loadaudio=\u8df3\u8fc7\u7f3a\u5931\u7684\u80cc\u666f\u97f3\u4e50: + +# AISolveCoordinator +aisolve.stop=\u505c\u6b62\u63a8\u6f14 +label.username=\u7528\u6237\u540d +label.password=\u5bc6\u7801 +button.login=\u767b\u5f55 +button.register=\u6ce8\u518c +button.guest=\u6e38\u5ba2\u767b\u5f55 +dialog.emptyfield=\u7528\u6237\u540d\u6216\u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a +dialog.syntax=\u7528\u6237\u540d\u53ea\u80fd\u5305\u542b\u6570\u5b57\u3001\u6c49\u5b57\u548c\u5927\u5c0f\u5199\u5b57\u6bcd\\n\u5bc6\u7801\u53ea\u80fd\u5305\u542b\u6570\u5b57\u548c\u5927\u5c0f\u5199\u5b57\u6bcd +dialog.invalid_login=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef +dialog.fail_login=\u8bfb\u53d6\u73a9\u5bb6\u6570\u636e\u5931\u8d25\uff1a +dialog.register_success=\u672c\u5730\u73a9\u5bb6\u521b\u5efa\u6210\u529f +dialog.register_taken=\u8be5\u7528\u6237\u540d\u5df2\u88ab\u6ce8\u518c +dialog.register_fail=\u4fdd\u5b58\u73a9\u5bb6\u6570\u636e\u5931\u8d25\uff1a diff --git a/src/main/java/Main.java b/src/main/java/Main.java index bec3e56..f9a014f 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,10 +1,18 @@ +import java.util.Locale; import view.MainFrame; import javax.swing.*; +import util.AppResources; // 游戏的主类,负责启动游戏 public class Main { public static void main(String[] args) { + for (String arg : args) { + if (arg.equals("en")) { + AppResources.setLocale(Locale.ENGLISH); + } + } + SwingUtilities.invokeLater(() -> new MainFrame(1532, 864).setVisible(true)); } } diff --git a/src/main/java/util/AppResources.java b/src/main/java/util/AppResources.java index ce80998..6d6b7e6 100644 --- a/src/main/java/util/AppResources.java +++ b/src/main/java/util/AppResources.java @@ -5,9 +5,15 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; /** Resolves bundled resources both from the classpath/JAR and a source checkout. */ public final class AppResources { + private static ResourceBundle messageBundle = + ResourceBundle.getBundle("resources/messages", Locale.SIMPLIFIED_CHINESE); + private AppResources() { } @@ -36,4 +42,17 @@ public static URL url(String path) { public static ImageIcon icon(String path) { return new ImageIcon(url(path)); } + + public static void setLocale(Locale locale) { + messageBundle = ResourceBundle.getBundle("resources/messages", locale); + } + + public static String get(String key) { + return messageBundle.getString(key); + } + + // For strings with placeholders like {0} + public static String get(String key, Object... args) { + return MessageFormat.format(messageBundle.getString(key), args); + } } diff --git a/src/main/java/view/MainFrame.java b/src/main/java/view/MainFrame.java index ff376b2..3b7f3e5 100644 --- a/src/main/java/view/MainFrame.java +++ b/src/main/java/view/MainFrame.java @@ -15,6 +15,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.Locale; // 游戏的主窗口,负责显示登录界面和游戏控制面板 public class MainFrame extends JFrame implements WindowListener { @@ -73,10 +74,16 @@ public void windowOpened(WindowEvent e) { public void windowClosing(WindowEvent e) { if (controlPanel != null && controlPanel.isVisible() && controlPanel.controller != null && controlPanel.controller.user != null) { if (controlPanel.controller.isAnimating()) { - JOptionPane.showMessageDialog(this, "请等待当前移动动画结束"); + JOptionPane.showMessageDialog(this, AppResources.get("mainframe.closing.animating")); return; } - int choice = JOptionPane.showConfirmDialog(this, "是否保存游戏?", "退出游戏", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + int choice = JOptionPane.showConfirmDialog( + this, + AppResources.get("mainframe.closing.confirm.save"), + AppResources.get("mainframe.closing.confirm.exit"), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE + ); if (JOptionPane.YES_OPTION == choice) { controlPanel.controller.saveGame(); exitApplication(); @@ -144,7 +151,7 @@ private List loadAudioFiles() { try { audioResources.add(AppResources.url(path)); } catch (IllegalArgumentException exception) { - System.out.println("跳过缺失的背景音乐: " + path); + System.out.println(AppResources.get("dialog.loadaudio") + path); } } return audioResources; diff --git a/src/main/java/view/login/LoginPanel.java b/src/main/java/view/login/LoginPanel.java index ada7b13..14d70bd 100644 --- a/src/main/java/view/login/LoginPanel.java +++ b/src/main/java/view/login/LoginPanel.java @@ -39,16 +39,16 @@ public LoginPanel(MainFrame mainFrame, int width, int height) { // 创建用户名和密码输入框 username = ViewUtil.createJTextField(contentPanel, new Point(width / 2 - 40, height / 2 - 100), 160, 30); password = ViewUtil.createJPasswordField(contentPanel, new Point(width / 2 - 40, height / 2 - 50), 160, 30); - ViewUtil.createJLabel(contentPanel, new Point(width / 2 - 110, height / 2 - 100), 70, 30, 16, "用户名:"); // 用户名标签 - ViewUtil.createJLabel(contentPanel, new Point(width / 2 - 110, height / 2 - 50), 70, 30, 16, "密码:"); // 密码标签 + ViewUtil.createJLabel(contentPanel, new Point(width / 2 - 110, height / 2 - 100), 70, 30, 16, AppResources.get("label.username").concat(":")); // 用户名标签 + ViewUtil.createJLabel(contentPanel, new Point(width / 2 - 110, height / 2 - 50), 70, 30, 16, AppResources.get("label.password").concat(":")); // 密码标签 ViewUtil.createJLabel(contentPanel, new Point(width / 2 - 110, height / 2 - 300), 240, 120, 60, "华 容 道"); // 游戏标题 // 登录注册按钮 Color btnColor = new Color(139, 69, 19); // 按钮颜色 Font btnFont = new Font("楷体", Font.PLAIN, 16); // 按钮字体 - JButton loginBtn = ViewUtil.createStyledButton(contentPanel, "登录", new Point(width / 2 - 80, height / 2 + 30), 80, 30, btnColor, btnFont); - JButton registerBtn = ViewUtil.createStyledButton(contentPanel, "注册", new Point(width / 2 + 20, height / 2 + 30), 80, 30, btnColor, btnFont); - JButton guestLoginBtn = ViewUtil.createStyledButton(contentPanel, "游客登录", new Point(width / 2 - 80, height / 2 + 80), 180, 32, btnColor, btnFont); + JButton loginBtn = ViewUtil.createStyledButton(contentPanel, AppResources.get("button.login"), new Point(width / 2 - 80, height / 2 + 30), 80, 30, btnColor, btnFont); + JButton registerBtn = ViewUtil.createStyledButton(contentPanel, AppResources.get("button.register"), new Point(width / 2 + 20, height / 2 + 30), 80, 30, btnColor, btnFont); + JButton guestLoginBtn = ViewUtil.createStyledButton(contentPanel, AppResources.get("button.guest"), new Point(width / 2 - 80, height / 2 + 80), 180, 32, btnColor, btnFont); loginBtn.addActionListener(event -> handleLogin()); // 登录按钮事件 registerBtn.addActionListener(event -> handleRegister()); // 注册按钮事件 guestLoginBtn.addActionListener(event -> mainFrame.showControl(null)); // 游客登录事件 @@ -63,11 +63,11 @@ protected void paintComponent(Graphics graphics) { // 验证用户名和密码 public boolean isValidCredentials(String user, String pass) { if (user.isEmpty() || pass.isEmpty()) { // 检查是否为空 - JOptionPane.showMessageDialog(this, "用户名或密码不能为空"); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.emptyfield")); } else if (Pattern.matches("^[a-zA-Z0-9一-龥]+$", user) && Pattern.matches("^[a-zA-Z0-9]+$", pass)) { return true; // 验证通过 } else { - JOptionPane.showMessageDialog(this, "用户名只能包含数字、汉字和大小写字母\n密码只能包含数字和大小写字母"); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.syntax")); } return false; } @@ -81,11 +81,11 @@ private void handleLogin() { if (users != null && users.verify(user, pass)) { mainFrame.showControl(user); // 显示控制面板 } else { - JOptionPane.showMessageDialog(this, "用户名或密码错误"); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.invalid_login")); } } } catch (IOException exception) { - JOptionPane.showMessageDialog(this, "读取玩家数据失败:" + exception.getMessage()); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.fail_login") + exception.getMessage()); } finally { Arrays.fill(pass, '\0'); } @@ -98,14 +98,14 @@ private void handleRegister() { try { if (isValidCredentials(user, new String(pass))) { if (users != null && users.register(user, pass)) { - JOptionPane.showMessageDialog(this, "本地玩家创建成功"); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.register_success")); mainFrame.showControl(user); } else { - JOptionPane.showMessageDialog(this, "该用户名已被注册"); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.register_taken")); } } } catch (IOException exception) { - JOptionPane.showMessageDialog(this, "保存玩家数据失败:" + exception.getMessage()); + JOptionPane.showMessageDialog(this, AppResources.get("dialog.register_fail") + exception.getMessage()); } finally { Arrays.fill(pass, '\0'); }