From c5acdc6e932d9b47571cb679da7c4945f7bdfda0 Mon Sep 17 00:00:00 2001 From: Hikari Date: Fri, 18 Oct 2024 19:04:04 +0800 Subject: [PATCH] =?UTF-8?q?v1.7.0=20=E5=9F=BA=E6=9C=AC=E7=9A=84=E5=A4=9A?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Author: Hikari16665 Co-authored-by: awaTsuki --- .github/workflows/build.yml | 80 ++++++++--- .gitignore | 4 +- hsl.json | 4 +- hsl/core/__init__.py | 4 +- hsl/core/checks.py | 35 +++-- hsl/core/config.py | 9 +- hsl/core/exceptions.py | 2 + hsl/core/locale.py | 45 ++++++ hsl/core/main.py | 3 +- hsl/core/sponsor.py | 3 +- hsl/utils/prompt.py | 12 +- lang/en.json | 116 +++++++++++++++ lang/zh.json | 116 +++++++++++++++ main.py | 275 ++++++++++++++++++------------------ 14 files changed, 525 insertions(+), 183 deletions(-) create mode 100644 hsl/core/locale.py create mode 100644 lang/en.json create mode 100644 lang/zh.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1b3b3a..f87e7f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,24 @@ on: branches: - main jobs: + tag_version: + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Bump version and push tag + id: tag + uses: anothrNick/github-tag-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CUSTOM_TAG: "v${{ vars.VERSION }}" + outputs: + new_tag: ${{ steps.tag.outputs.new_tag }} build: strategy: matrix: os: [ubuntu-20.04, windows-latest] - + needs: tag_version runs-on: ${{ matrix.os }} steps: @@ -46,6 +59,7 @@ jobs: - name: Build executable(Linux) if: ${{ matrix.os == 'ubuntu-20.04' }} uses: Nuitka/Nuitka-Action@main + with: nuitka-version: main script-name: main.py @@ -59,30 +73,64 @@ jobs: product-version: ${{ vars.VERSION }} file-description: HikariServerLauncher copyright: "Copyright 2024 Hikari" + include-data-files: lang/*.json=lang/ - name: Upload executable uses: actions/upload-artifact@v3 with: + name: ${{ runner.os}} Build path: | build/*.exe build/*.bin - build/*.app/**/* - - name: Bump version and push tag - id: tag_version - if: ${{ matrix.os == 'ubuntu-20.04' }} - uses: anothrNick/github-tag-action@v1 + create_release: + needs: ['build', 'tag_version'] + runs-on: ubuntu-20.04 + steps: + - name: Download executable + uses: actions/download-artifact@v3 + - name: Create Release + id: create_release + uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CUSTOM_TAG: "v${{ vars.VERSION }}" - - name: Create Release - uses: softprops/action-gh-release@v1 with: - tag_name: ${{ steps.tag_version.outputs.new_tag }} - files: | - build/*.exe - build/*.bin - build/*.app/**/* + tag_name: ${{ needs.tag_version.outputs.new_tag }} + release_name: Release ${{ needs.tag_version.outputs.new_tag }} body: | - Release ${{ vars.VERSION }} - Automatically generated release from tag ${{ steps.tag_version.outputs.new_tag }} + HikariServerLauncher Release ${{ needs.tag_version.outputs.new_tag }} + Release automatically by Github Actions + draft: false + prerelease: false + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + upload_release_asset: + needs: ['tag_version', 'create_release'] + strategy: + matrix: + os: [ubuntu-20.04, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Download executable + uses: actions/download-artifact@v3 + - name: Upload Release Asset(Linux) + if: ${{ matrix.os == 'ubuntu-20.04' }} + id: upload_release_asset_linux + uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + asset_content_type: application/octet-stream + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: build/HikariServerLauncher-${{ needs.tag_version.outputs.new_tag }}.bin + asset_name: HikariServerLauncher-${{ needs.tag_version.outputs.new_tag }}.bin + - name: Upload Release Asset(Windows) + if: ${{ matrix.os == 'windows-latest' }} + id: upload_release_asset_win + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + asset_content_type: application/octet-stream + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: build/HikariServerLauncher-${{ needs.tag_version.outputs.new_tag }}.exe + asset_name: HikariServerLauncher-${{ needs.tag_version.outputs.new_tag }}.exe + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7c31615..9a7cb46 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ hsl-config.json *.build/ *.dist/ nuitka-crash-report.xml -*.bin \ No newline at end of file +*.bin +TODO +Dockerfile \ No newline at end of file diff --git a/hsl.json b/hsl.json index e257346..5794d1c 100644 --- a/hsl.json +++ b/hsl.json @@ -1,4 +1,4 @@ { - "version": 16, - "minor": 5 + "version": 17, + "minor": 0 } \ No newline at end of file diff --git a/hsl/core/__init__.py b/hsl/core/__init__.py index 71c8fb9..67dd742 100644 --- a/hsl/core/__init__.py +++ b/hsl/core/__init__.py @@ -3,4 +3,6 @@ from . import main from . import server from . import workspace -from . import sponsor \ No newline at end of file +from . import sponsor +from . import exceptions +from . import locale \ No newline at end of file diff --git a/hsl/core/checks.py b/hsl/core/checks.py index aafb7cb..58fe65e 100644 --- a/hsl/core/checks.py +++ b/hsl/core/checks.py @@ -1,36 +1,33 @@ -from functools import cache -import logging -import requests -import os +import httpx from rich.console import Console -logger = logging.getLogger(__name__) -DOWNLOAD_SOURCE = r'https://hsl.hikari.bond/source.json' -SPCONFIGS_SOURCE = r'https://hsl.hikari.bond/spconfigs.json' +# DOWNLOAD_SOURCE = r'https://hsl.hikari.bond/source.json' +# SPCONFIGS_SOURCE = r'https://hsl.hikari.bond/spconfigs.json' VERSION_SOURCE = r'https://hsl.hikari.bond/hsl.json' -HSL_VERSION = 16 -HSL_VERSION_MINOR = 5 +HSL_VERSION = 17 +HSL_VERSION_MINOR = 0 console = Console() async def make_request(url: str, error_message: str) -> dict: - console.print(f'Requesting: {url}') + #console.print(f'Requesting: {url}') try: - response = requests.get(url,timeout=3) - if response.status_code == 200: - return response.json() - console.log(f'{error_message},状态码:{response.status_code}') - return {} - except Exception as e: - console.log(f'{error_message},错误信息:{e}') + async with httpx.AsyncClient() as client: + response = await client.get(url) + if response.status_code == 200: + return response.json() + console.print(f'[bold red]{error_message}[/bold red]') + return {} + except httpx.ConnectError: + console.print(f'[bold red]{error_message}[/bold red]') return {} async def check_update(): data = await make_request(VERSION_SOURCE, error_message='检查更新失败') latest: int = data.get('version', 0) minor: int = data.get('minor', 0) - console.log(f'Version data fetched: {latest}-{minor}') + #console.log(f'Version data fetched: {latest}-{minor}') if (HSL_VERSION < latest): console.print(f'[bold magenta]发现主要版本更新,版本号:[u]{latest/10}[/u],建议及时更新') return - if (HSL_VERSION_MINOR < minor): + if (HSL_VERSION_MINOR < minor and HSL_VERSION == latest): console.print(f'[bold magenta]发现次要版本更新,版本号:[u]{minor/10}[/u],建议及时更新') return def get_version() -> tuple: diff --git a/hsl/core/config.py b/hsl/core/config.py index 006eceb..bda5725 100644 --- a/hsl/core/config.py +++ b/hsl/core/config.py @@ -1,8 +1,10 @@ import json import logging import os +from rich.console import Console logger = logging.getLogger('hsl') CONFIG_FILE = 'hsl-config.json' +console = Console() class Config(): def __init__(self): self.first_run: bool = True @@ -12,6 +14,7 @@ def __init__(self): self.backup_dir: str = 'backup' self.autorun: str = '' self.debug: bool = False + self.language: str = 'en' def load(self): try: with open(CONFIG_FILE, 'r') as f: @@ -21,12 +24,12 @@ def load(self): pass except KeyError: os.remove(CONFIG_FILE) - logger.error('Invalid config file, removed and created a new one') + console.log('Invalid config file, removed and created a new one') self.save() except json.JSONDecodeError: - logger.error('Invalid config file') + console.log('Invalid config file') return self def save(self): with open(CONFIG_FILE, 'w') as f: json.dump(self.__dict__, f, indent=4) - logger.info(f'Config saved to {CONFIG_FILE} with data {self.__dict__}') \ No newline at end of file + console.log(f'Config saved to {CONFIG_FILE} with data {self.__dict__}') \ No newline at end of file diff --git a/hsl/core/exceptions.py b/hsl/core/exceptions.py index b94edae..565a473 100644 --- a/hsl/core/exceptions.py +++ b/hsl/core/exceptions.py @@ -1,2 +1,4 @@ class NoSuchServerException(Exception): + pass +class LanguageNotSupportedException(Exception): pass \ No newline at end of file diff --git a/hsl/core/locale.py b/hsl/core/locale.py new file mode 100644 index 0000000..a3d2533 --- /dev/null +++ b/hsl/core/locale.py @@ -0,0 +1,45 @@ +# HSL Locale Module +from typing import Any, Union +from hsl.core.exceptions import LanguageNotSupportedException +import os +import sys +import json +from rich.console import Console +from hsl.core.config import Config +console = Console() +class Locale(): + def __init__(self): + self.config = Config().load() + self.language = self.config.language + self.debug = self.config.debug + self.language_key = {} + self.set_language(self.language) + def set_language(self, language: str) -> None: + self.language = language + self.packaged = bool(getattr(sys, '_MEIPASS', False)) + base_path = sys._MEIPASS if self.packaged else os.path.abspath(".") # type: ignore + file_path = os.path.join(base_path, 'lang', f'{self.language}.json') + if self.debug: + console.log(f'Loading language file {file_path}') + try: + with open(file_path, 'r', encoding='utf-8') as f: + self.language_key = json.load(f) + except FileNotFoundError as e: + raise LanguageNotSupportedException( + f'Language {self.language} is not supported.' + ) from e + def replace(self, text: str, replaces: dict[str, str] = {}) -> str: + for k, v in replaces.items(): + text = text.replace(f'{{{k}}}', v) + return text + def trans_key(self, key: Union[str, list[str]], /, **replaces: str) -> Any: # type: ignore + if isinstance(key, str): + #console.log(f'Translating key {key} in {self.language} replaces: {replaces}') + return self.replace(self.language_key.get(key, key), replaces) + if isinstance(key, list): + _texts = [] + for k in key: + #console.log(f'Translating key {k} in {self.language} replaces: {replaces}') + _texts.append(self.language_key.get(k, k)) + return [self.replace(text, replaces) for text in _texts] + return None \ No newline at end of file diff --git a/hsl/core/main.py b/hsl/core/main.py index 28fc5ba..f8f63bb 100644 --- a/hsl/core/main.py +++ b/hsl/core/main.py @@ -1,4 +1,5 @@ from hsl.core.config import Config +from hsl.core.locale import Locale from hsl.core.checks import get_version from hsl.source.source import get_source from hsl.spconfigs.spconfigs import get_spconfigs @@ -6,7 +7,7 @@ import sys import platform console = Console() - +locale = Locale() OS_ARCH = platform.machine() console.log(f'OS_ARCH: {OS_ARCH}') if OS_ARCH not in ['AMD64', 'x86_64']: diff --git a/hsl/core/sponsor.py b/hsl/core/sponsor.py index 0d73e6b..f215ade 100644 --- a/hsl/core/sponsor.py +++ b/hsl/core/sponsor.py @@ -1,7 +1,8 @@ SPONSOR = [ "Hikari", "awatsuki", - "BenXong" + "BenXong", + "Graphite_sm" ] def get_sponsor_list() -> list: return SPONSOR \ No newline at end of file diff --git a/hsl/utils/prompt.py b/hsl/utils/prompt.py index 512b7ac..bedb702 100644 --- a/hsl/utils/prompt.py +++ b/hsl/utils/prompt.py @@ -1,17 +1,19 @@ from noneprompt import ListPrompt, InputPrompt, Choice +from hsl.core.locale import Locale import asyncio -OPTIONS_YN = ['是', '否'] +locale = Locale() +OPTIONS_YN = locale.trans_key(['yes','no']) async def promptSelect(options: list,prompt: str) -> int: choices = [Choice(options[i],data=i) for i in range(len(options))] - prompt_task = asyncio.create_task(ListPrompt(prompt, choices=choices).prompt_async()) + prompt_task = asyncio.create_task(ListPrompt(prompt, choices=choices,annotation=locale.trans_key('choice-prompt-annotation')).prompt_async()) select = await asyncio.gather(prompt_task) return select[0].data async def promptInput(prompt: str) -> str: prompt_task = asyncio.create_task(InputPrompt(prompt, validator=lambda string: True).prompt_async()) - input = await asyncio.gather(prompt_task) - return input[0] + _input = await asyncio.gather(prompt_task) + return _input[0] async def promptConfirm(prompt: str) -> bool: # prompt_task = asyncio.create_task(ConfirmPrompt(prompt,default_choice=False).prompt_async()) # confirm = await asyncio.gather(prompt_task) # return confirm[0] - return True if await promptSelect(OPTIONS_YN,prompt) == 0 else False \ No newline at end of file + return bool(await promptSelect(OPTIONS_YN,prompt) == 0) \ No newline at end of file diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..f26ca32 --- /dev/null +++ b/lang/en.json @@ -0,0 +1,116 @@ +{ + "title": "HikariServerLauncher [bold blue]v{version} ", + "title-debug": "[white]- [bold red]Debug Mode", + "version": "[bold blue]You're running {HSL_NAME} version:[u]{version}[/u] minor versions:[u]{minor_version}[/u]", + "set-language": "Language", + "select-language": "Select the language:", + "language-changed": "The language has changed. Some changes may need a restart to take effect.", + "sponsor": "Sponsor", + "sponsor-list": " The Sponsor List", + "sponsor-thanks": "[bold blue]Thank you to all the above-mentioned individuals for their support of this project!", + "exit": "Exit", + "cancel": "Cancel", + "yes": "Yes", + "no": "No", + "return": "Return", + "settings": "Settings", + "advanced-settings": "Advanced-setting", + "cannot-open-web-browser": "Unable to open the help document. Please refer to {help} or join the QQ group {qqgroup} for assistance.", + "java-6": "Java 6", + "java-8": "Java 8", + "java-11": "Java 11", + "java-17": "Java 17", + "java-21": "Java 21", + "vanilla": "Vanilla", + "forge": "Forge", + "fabric": "Fabric", + "paper": "Paper", + "debug": "Debug", + "mirror-first": "Mirror Priority", + "run-on-system-startup": "Startup Programs", + "backup-server": "Backup Server", + "restore-server": "Restore Server", + "delete-backup": "Delete Backup", + "create-server": "Create Server", + "manage-server": "Manage Server", + "delete-server": "Delete Server", + "start-server": "Start Server", + "install-server-failed": "[bold magenta]Installation of the server failed.", + "advanced-options": "Advanced Options", + "backup-center": "Backup Center", + "open-server-folder": "Open The Server Folder", + "specific-configs": "Specific Configuration", + "command-execute-before-server-start": "Commands Executed Before The Server Start", + "command-execute-before-server-start-prompt-input": "Please enter the command that will be executed in the server directory before the server starts.", + "custom-jvm-args": "Customize JVM parameters", + "set-to-auto-run": "Set To Auto-run", + "set-to-auto-run-ask": "Are you sure you want to set {servername} to auto-run?", + "set-to-auto-run-2": "[bold green]Auto-run setting successful, the server will automatically run the next time you run this software.", + "export-start-script": "Export Startup Script", + "edit-java-version": "Edit Java Version", + "edit-max-ram": "Change Maximum Memory", + "server-exist": "[bold magenta]Server already exists.", + "customize-on-first-run": "Initial Run Configuration", + "settings-applied": "Settings have been applied.", + "finished-customizing": "Setup is complete.", + "set-mirror-priority-suggest-text": "If your server environment is in China, it is recommended to use a mirror source for better speed.", + "set-mirror-priority-prompt-select": "Would you like to prioritize using a mirror source?", + "no-such-server": "Server does not exist.", + "server-install-failed": "Server installation failed.", + "server-install-success": "[bold green]Server {serverName} installation succeed.", + "paper-experimental-prompt-select": "Would you like to download the experimental build version?", + "server-type-prompt-select": "Please select the server type:", + "server-name-input": "Please enter the server name:", + "illegal-name-input": "The name is invalid, please re-enter:", + "maximum-memory-prompt-input": "The maximum memory of your computer is:{OS_MAXRAM}MB.Please enter the maximum memory for the server (example: 1024M or 1G):", + "mirror-first-enabled-prompt-text": "[bold red]Priority mirror source setting has been enabled. Some downloads will be prioritized to use the mirror source. If the download fails, please try switching to the official source.", + "maximum-memory-input-illegal": "Incorrect input, please re-enter:", + "no-server-available": "[bold magenta]No available servers.", + "server-management": "Server Management", + "select-server": "Select server:", + "server-manage-prompt-select": "{servername} - Please select an operation:", + "no-specific-config-available": "[bold magenta]There are no editable configuration files.", + "specific-config-file-edit-select": "Please select the configuration file you want to modify:", + "specific-config-edit-select": "Please select the configuration item you want to modify:", + "danger-config-warn": "[bold red]This is a dangerous configuration! Think twice before modifying!", + "maximum-memory-setting-success": "[bold green]Maximum memory setting successful.", + "prompt-select-java-automatically": "[bold red]Note: The server will automatically use the recommended Java version. Arbitrary changes may cause the server to fail to start.", + "specific-config-current-value": "[bold green]{name} - Current value: {value}", + "java-select": "Please select a Java version.", + "java-select-success": "[bold green]Java version setting successful.", + "exported-start-script": "[green]Startup script generated successfully.({script_name})", + "startup-cmd-set": "[bold green]Command setting successful.", + "cannot-open-server-directory": "[bold magenta]Unable to open the server directory.", + "jvm-setting": "[white bold]Please enter JVM parameters (including the hyphen, for example, -Xms1G, multiple parameters can be added), which will be added to the startup parameters when the server starts. By default, -Dfile.encoding=utf-8 and -Xmx are already set.", + "enter-new-value-int": "Please enter a new value (integer):", + "enter-new-value-str": "Please enter a new value (string):", + "enter-new-value-bool": "Please select a new value (yes or no):", + "enter-new-value-select": "Please enter a new value:", + "delete-server-prompt-confirm": "Are you sure you want to delete?", + "delete-server-prompt-select": "Please select the server you want to delete:", + "already-in-autorun-registry": "[bold green]Hikari Server Launcher is already set to auto-run on boot, no need to set it again.", + "autorun-registry-remove-prompt-confirm": "Do you want to remove it from the boot auto-run?", + "autorun-registry-remove-success": "[bold green]It has been removed from the boot auto-run.", + "autorun-registry-add-prompt-confirm": "Do you want to add Hikari Server Launcher to the boot auto-run?", + "autorun-not-supported-os": "[bold magenta]The current operating system does not support boot auto-run.", + "server-auto-run-prompt": "[bold blue] {servername} will start in three seconds. Press Ctrl+C (^C) to cancel.", + "jvm-setting-prompt": "This is an advanced setting. If you are not familiar with it, please do not fill it out casually.", + "jvm-setting-success": "[bold green]JVM parameter setting successful.", + "debug-mode-prompt-select": "Enable debug mode?", + "user-cancel-operate": "[bold green]The user has canceled the operation, and the program has exited.", + "unknown-error-occur": "[bold red]An unknown error occurred: {e} ", + "no-backup-available": "[bold magenta]There are no available backups.", + "delete-backup-prompt-select": "Please select the backup you want to delete:", + "delete-backup-prompt-confirm": "Are you sure you want to delete {backupname} ?", + "backup-restore-select": "Please select the backup you want to restore:", + "backup-restoring": "Restoring backup {backupname}...", + "backup-restore-success": "[bold green]The backup {backupname} of {servername} has been successfully restored.", + "backup-restore-server-prompt-select": "Please select the server to restore the backup to:", + "backup-server-prompt-select": "Please select the server you want to back up:", + "autorun-canceled": "Auto-start has been canceled and reset. If you need to enable it again, please set it up again.", + "backup-creating": "{servername} Creating a backup...", + "backup-create-success": "[bold green]The backup {backupname} of {servername} has been successfully created.", + "menu": "Menu:", + "backup-management": "Backup Management", + "choice-prompt-annotation": "Use ↑ and ↓ to choose, Enter to submit." +} \ No newline at end of file diff --git a/lang/zh.json b/lang/zh.json new file mode 100644 index 0000000..d6e5bc5 --- /dev/null +++ b/lang/zh.json @@ -0,0 +1,116 @@ +{ + "title": "HikariServerLauncher [bold blue]v{version} ", + "title-debug": "[white]- [bold red]调试模式", + "version": "[bold blue]你正在运行{HSL_NAME} 版本号:[u]{version}[/u],次要版本:[u]{minor_version}[/u]", + "set-language": "设置语言", + "select-language": "选择语言:", + "language-changed": "语言已更改。部分文本需要重启软件才能得到更新。", + "sponsor": "赞助者", + "sponsor-list": "赞助者名单", + "sponsor-thanks": "[bold blue]感谢以上人员对本项目的支持!", + "exit": "退出", + "cancel": "取消", + "yes": "是", + "no": "否", + "return": "返回", + "settings": "设置", + "advanced-settings": "高级选项", + "cannot-open-web-browser": "[bold magenta]无法打开帮助文档。请自行查阅{help},或加入QQ群{qqgroup}获取帮助。", + "java-6": "Java 6", + "java-8": "Java 8", + "java-11": "Java 11", + "java-17": "Java 17", + "java-21": "Java 21", + "vanilla": "原版", + "forge": "Forge", + "fabric": "Fabric", + "paper": "Paper", + "debug": "调试模式", + "mirror-first": "镜像优先", + "run-on-system-startup": "开机启动", + "backup-server": "备份服务器", + "restore-server": "恢复服务器", + "delete-backup": "删除备份", + "create-server": "创建服务器", + "manage-server": "管理服务器", + "delete-server": "删除服务器", + "start-server": "启动服务器", + "install-server-failed": "[bold magenta]安装服务器失败。", + "advanced-options": "高级选项", + "backup-center": "备份中心", + "open-server-folder": "打开服务器文件夹", + "specific-configs": "特定配置", + "command-execute-before-server-start": "启动前执行命令", + "command-execute-before-server-start-prompt-input": "请输入命令,将在服务器启动前在服务器目录执行:", + "custom-jvm-args": "自定义JVM参数", + "set-to-auto-run": "设置为自动启动", + "set-to-auto-run-ask": "确定要将 {servername} 设为自动启动吗?", + "set-to-auto-run-2": "[bold green]自动启动设置成功,将在下次运行此软件时自动打开该服务器。", + "export-start-script": "导出启动脚本", + "edit-java-version": "更改Java版本", + "edit-max-ram": "更改最大内存", + "server-exist": "[bold magenta]服务器已存在。", + "customize-on-first-run": "初次运行配置", + "settings-applied": "设置已应用。", + "finished-customizing": "已完成设置。", + "set-mirror-priority-suggest-text": "如果你的服务器环境在国内, 推荐使用镜像源以获得更好的速度。", + "set-mirror-priority-prompt-select": "是否使用镜像源优先?", + "no-such-server": "服务器不存在。", + "server-install-failed": "服务器安装失败。", + "server-install-success": "[bold green]服务器 {serverName} 安装成功。", + "paper-experimental-prompt-select": "是否下载实验性构建版本?", + "server-type-prompt-select": "请选择服务器类型:", + "server-name-input":"请输入服务器名称:", + "illegal-name-input":"名称非法,请重新输入:", + "maximum-memory-prompt-input":"你的主机最大内存为:{OS_MAXRAM}MB 请输入服务器最大内存(示例:1024M 或 1G):", + "mirror-first-enabled-prompt-text": "[bold red]镜像源优先设置已启用,部分下载将优先使用镜像源。若无法正常下载,请尝试切换至官方源。", + "maximum-memory-input-illegal":"输入错误,请重新输入:", + "no-server-available":"[bold magenta]没有可用的服务器。", + "server-management":"服务器管理", + "select-server":"选择服务器:", + "server-manage-prompt-select": "{servername} - 请选择操作:", + "no-specific-config-available": "[bold magenta]没有可编辑的配置文件。", + "specific-config-file-edit-select": "请选择要修改的配置文件:", + "specific-config-edit-select": "请选择要修改的配置项:", + "danger-config-warn": "[bold red]这是一个危险配置!修改前请三思!", + "maximum-memory-setting-success":"[bold green]最大内存设置成功。", + "prompt-select-java-automatically":"[bold red]注意:服务器将自动使用推荐的Java版本,随意修改可能会导致服务器无法启动。", + "specific-config-current-value": "[bold green]{name} - 当前值: {value}", + "java-select":"请选择Java版本", + "java-select-success":"[bold green]Java版本设置成功。", + "exported-start-script": "[green]生成启动脚本成功({script_name})", + "startup-cmd-set": "[bold green]命令设置成功。", + "cannot-open-server-directory":"[bold magenta]无法打开服务器目录。", + "jvm-setting":"[white bold]请输入JVM参数(包含横杠,例如-Xms1G,可多个),将在服务器启动时添加至启动参数内 默认已设置-Dfile.encoding=utf-8以及-Xmx", + "enter-new-value-int": "请输入新值(整数):", + "enter-new-value-str": "请输入新值(字符串):", + "enter-new-value-bool": "请选择新值(是否):", + "enter-new-value-select": "请选择新值:", + "delete-server-prompt-confirm": "确定要删除吗?", + "delete-server-prompt-select": "请选择要删除的服务器:", + "already-in-autorun-registry": "[bold green]Hikari Server Launcher 已在开机自启,无需重复设置。", + "autorun-registry-remove-prompt-confirm": "要从开机自启中移除吗?", + "autorun-registry-remove-success": "[bold green]已从开机自启中移除。", + "autorun-registry-add-prompt-confirm": "要将 Hikari Server Launcher 加入开机自启吗?", + "autorun-not-supported-os": "[bold magenta]当前操作系统不支持开机自启。", + "server-auto-run-prompt": "[bold blue]将于三秒后启动 {servername}。键入Ctrl+C(^C)可取消", + "jvm-setting-prompt": "此为高级设置,若您不了解请勿随意填写:", + "jvm-setting-success":"[bold green]JVM参数设置成功。", + "debug-mode-prompt-select": "是否启用调试模式?", + "user-cancel-operate":"[bold green]用户取消操作,已退出。", + "unknown-error-occur":"[bold red]发生未知错误: {e}", + "no-backup-available":"[bold magenta]没有可用的备份。", + "delete-backup-prompt-select": "请选择要删除的备份:", + "delete-backup-prompt-confirm": "确定要删除 {backupname} 吗?", + "backup-restore-select": "请选择要恢复的备份:", + "backup-restoring": "正在恢复备份 {backupname}...", + "backup-restore-success": "[bold green]{servername} 的备份 {backupname} 恢复成功。", + "backup-restore-server-prompt-select": "请选择要恢复备份的服务器:", + "backup-server-prompt-select": "请选择要备份的服务器:", + "autorun-canceled": "自动启动已取消并重置,如需再次启用请重新设置。", + "backup-creating": "{servername}正在创建备份...", + "backup-create-success": "[bold green]{servername} 的备份 {backupname} 创建成功。", + "menu":"菜单:", + "backup-management":"备份管理", + "choice-prompt-annotation": "(使用键盘↑, ↓键选择,Enter键确认。)" +} \ No newline at end of file diff --git a/main.py b/main.py index 7fd5ff7..b0ddac0 100644 --- a/main.py +++ b/main.py @@ -4,9 +4,11 @@ import yaml import asyncio import noneprompt + +from hsl.core import backup try: import winreg as reg -except: +except Exception: pass import webbrowser import javaproperties @@ -14,24 +16,42 @@ from hsl.core.server import Server from hsl.core.java import Java from hsl.core.checks import check_update -from typing import Callable +from typing import Any, Callable, Optional from rich.table import Table from hsl.core.workspace import Workspace from hsl.core.main import HSL +from hsl.core.locale import Locale from hsl.core.backup import Backup from hsl.core.sponsor import get_sponsor_list from rich.console import Console from hsl.gametypes import fabric, forge, paper, vanilla from hsl.utils.prompt import promptSelect, promptInput, promptConfirm +locale = Locale() +console = Console() HELP_URL = r'https://docs.qq.com/doc/DY3pnS1hFVm1uYWlp' -OPTIONS_ADVANCED = ['取消'] -OPTIONS_SETTINGS = ['调试模式', '镜像源优先', '开机自启', '取消'] -OPTIONS_GAMETYPE = ['原版','Paper','Forge','Fabric','取消'] -OPTIONS_BACKUPS = ['备份服务器', '还原服务器', '删除备份', '取消'] -OPTIONS_MENU = ['创建服务器', '管理服务器', '删除服务器', '备份中心', '设置', '高级选项','赞助者名单', '退出'] -OPTIONS_MANAGE = ['启动服务器','打开服务器目录','特定配置',"启动前执行命令",'自定义JVM设置','设定为自动启动', '导出启动脚本', '更改Java版本', '更改最大内存', '取消'] -OPTIONS_JAVA = ['Java 6', 'Java 8', 'Java 11', 'Java 16', 'Java 17','Java 21', '取消'] -OPTIONS_JAVA_VERSION = ['6', '8', '11', '16', '17', '21'] +QQGROUP_URL = r'https://qm.qq.com/q/bUTqWXnwje' +def constants_init() -> None: + global OPTIONS_MENU, OPTIONS_MANAGE, OPTIONS_BACKUPS, OPTIONS_SETTINGS, OPTIONS_ADVANCED, OPTIONS_GAMETYPE, OPTIONS_JAVA, OPTIONS_JAVA_VERSION + OPTIONS_ADVANCED = locale.trans_key(['cancel']) + OPTIONS_SETTINGS = locale.trans_key( + ['set-language', 'debug', 'mirror-first', 'run-on-system-startup', 'cancel'] + ) + OPTIONS_GAMETYPE = locale.trans_key( + ['vanilla', 'paper', 'forge', 'fabric', 'cancel'] + ) + OPTIONS_BACKUPS = locale.trans_key( + ['backup-server', 'restore-server', 'delete-backup', 'cancel'] + ) + OPTIONS_MENU = locale.trans_key( + ['create-server', 'manage-server', 'delete-server', 'backup-center', 'settings', 'advanced-options', 'sponsor-list', 'exit'] + ) + OPTIONS_MANAGE = locale.trans_key(['start-server','open-server-folder','specific-configs','command-execute-before-server-start','custom-jvm-args','set-to-auto-run','export-start-script','edit-java-version','edit-max-ram'])#['启动服务器','打开服务器目录','特定配置',"启动前执行命令",'自定义JVM设置','设定为自动启动', '导出启动脚本', '更改Java版本', '更改最大内存', '取消'] + OPTIONS_JAVA = locale.trans_key( + ['java-6', 'java-8', 'java-11', 'java-17', 'java-21', 'cancel'] + ) + OPTIONS_JAVA_VERSION = ['6', '8', '11', '16', '17', '21'] +OPTIONS_LANGUAGE = ["简体中文", "English"] +OPTIONS_LANGUAGE_CODE = ["zh", "en"] OS_MAXRAM = osfunc.getOSMaxRam() #max ram in MB HSL_NAME = 'Hikari Server Launcher' MAXRAM_PATTERN = re.compile(r'^\d+(\.\d+)?(M|G)$') # like 4G or 4096M @@ -40,36 +60,34 @@ AUTORUN_REG_PATH = r"Software\Microsoft\Windows\CurrentVersion\Run" except NameError: pass -console = Console() - class HSL_MAIN(HSL): def __init__(self): super().__init__() self.Workspace = Workspace() self.Java = Java() self.Backup = Backup() + constants_init() async def welcome(self) -> None: """ Welcome """ try: + webbrowser.open(QQGROUP_URL, new=1) webbrowser.open(HELP_URL, new=1) except webbrowser.Error: - console.print(f'[bold magenta]无法打开帮助文档。请自行查阅{HELP_URL}') - console.rule('配置设置') - console.print('如果你的服务器环境在国内, 推荐使用镜像源源以获得更好的速度。\n是否使用镜像源优先? (默认: 否)\n') - self.config.use_mirror = await promptConfirm('是否使用镜像源优先?') + console.print(locale.trans_key('cannot-open-web-browser', help = HELP_URL, qqgroup = QQGROUP_URL)) + await self.set_language() + console.rule(locale.trans_key('customize-on-first-run')) + console.print(locale.trans_key('set-mirror-priority-suggest-text')) + self.config.use_mirror = await promptConfirm(locale.trans_key('set-mirror-priority-prompt-select')) self.config.first_run = False self.config.save() - console.print('设置已应用。') - console.rule('配置完成') + console.print(locale.trans_key('settings-applied')) + console.rule(locale.trans_key('finished-customizing')) return async def exit(self) -> None: sys.exit(0) async def do_nothing(self) -> None: - """ - Do nothing - """ pass async def create(self) -> None: """ @@ -77,14 +95,14 @@ async def create(self) -> None: """ serverName = await self.get_valid_server_name() if not serverName: - console.print('[bold magenta]服务器已存在。') + console.print(locale.trans_key('server-exist')) return - console.print('服务器不存在,进入安装阶段。') + console.print(locale.trans_key('no-such-server')) serverPath = await self.Workspace.create(server_name=serverName) server_setting = await self.install(serverName=serverName, serverPath=serverPath) if not server_setting: - console.print('[bold magenta]未安装服务器。') + console.print(locale.trans_key('install-server-failed')) return serverName, serverType, serverPath, javaversion, data = server_setting # type: ignore @@ -92,53 +110,37 @@ async def create(self) -> None: await self.Workspace.add( Server(name=serverName, type=serverType, path=serverPath, javaversion=javaversion, maxRam=maxRam, data=data) ) - console.print(f'[bold green]服务器 {serverName} 安装成功。') + console.print(locale.trans_key('server-install-success', serverName = serverName)) - async def get_valid_server_name(self) -> str | None: + async def get_valid_server_name(self) -> Optional[str]: """ Get valid server name Return: str | None """ - serverName = await promptInput('请输入服务器名称:') + serverName = await promptInput(locale.trans_key('server-name-input')) while (not serverName.strip()) or (serverName in ['con','aux','nul','prn'] and os.name == 'nt'): - serverName = await promptInput('名称非法,请重新输入:') + serverName = await promptInput(locale.trans_key('illegal-name-input')) servers = self.Workspace.workspaces return None if any(s['name'] == serverName for s in servers) else serverName async def get_valid_max_ram(self) -> str: - """ - Get valid max ram - Return: str | None - """ - maxRam = await promptInput(f'你的主机最大内存为:{OS_MAXRAM}MB 请输入服务器最大内存(示例:1024M 或 1G):') + maxRam = await promptInput(locale.trans_key('maximum-memory-prompt-input',OS_MAXRAM = str(OS_MAXRAM))) while not MAXRAM_PATTERN.match(maxRam): - maxRam = await promptInput('输入错误,请重新输入:') + maxRam = await promptInput(locale.trans_key('maximum-memory-input-illegal')) return maxRam - async def install(self, *, serverName: str, serverPath: str) -> tuple | bool: - """ - Install the server - Args: - serverName: str - serverPath: str - - Returns: - serverName, - serverPath, - serverJarPath, - data - """ + async def install(self, *, serverName: str, serverPath: str) -> tuple[str, str, str, str, dict] | bool: if self.config.use_mirror: - console.print('[bold red]镜像源优先设置已启用,部分下载将优先使用镜像源。若无法正常下载,请尝试切换至官方源。') + console.print(locale.trans_key("mirror-first-enabled-prompt-text")) serverJarPath = os.path.join(serverPath, 'server.jar') - gameType = await promptSelect(OPTIONS_GAMETYPE, '请选择服务器类型:') + gameType = await promptSelect(OPTIONS_GAMETYPE, locale.trans_key("server-type-prompt-select")) if gameType == 0: return await vanilla.install(self, serverName, serverPath, serverJarPath, {}) elif gameType == 1: data = { - 'experimental': await promptConfirm('是否下载实验性构建版本?') + 'experimental': await promptConfirm(locale.trans_key('paper-experimental-prompt-select')) } return await paper.install(self, serverName, serverPath, serverJarPath, data) elif gameType == 2: @@ -159,14 +161,14 @@ async def install(self, *, serverName: str, serverPath: str) -> tuple | bool: # return await install_methods[gameType](self, serverName, serverPath, serverJarPath, data) async def manage(self) -> None: if not self.Workspace.workspaces: - console.print('[bold magenta]没有可用的服务器。') + console.print(locale.trans_key('no-server-available')) await asyncio.sleep(1) return - console.rule('服务器管理') - index = await promptSelect([x['name'] for x in self.Workspace.workspaces], '选择服务器:') + console.rule(locale.trans_key('server-management')) + index = await promptSelect([x['name'] for x in self.Workspace.workspaces], locale.trans_key('select-server')) server = await self.Workspace.get(index) - _index = await promptSelect(OPTIONS_MANAGE, f'{server.name} - 请选择操作:') + _index = await promptSelect(OPTIONS_MANAGE, locale.trans_key('server-manage-prompt-select', servername = server.name)) manage_methods: dict[int, Callable] = { 0: lambda: server.run(self.Workspace.dir), @@ -185,57 +187,52 @@ async def manage(self) -> None: async def change_max_ram(self, index: int) -> None: maxRam = await self.get_valid_max_ram() await self.Workspace.modify(index,'maxRam', maxRam) - console.print('[bold green]最大内存设置成功。') + console.print(locale.trans_key('maximum-memory-setting-success')) async def change_java_version(self, index: int): - console.print('[bold red]注意:服务器将自动使用推荐的Java版本,随意修改可能会导致服务器无法启动。') - _index_java = await promptSelect(OPTIONS_JAVA, '请选择Java版本:') + console.print(locale.trans_key('prompt-select-java-automatically')) + _index_java = await promptSelect(OPTIONS_JAVA, locale.trans_key('java-select')) if _index_java == len(OPTIONS_JAVA)-1: return await self.Workspace.modify(index, 'javaversion', OPTIONS_JAVA_VERSION[_index_java]) - console.print('[bold green]Java版本设置成功。') + console.print(locale.trans_key('java-select-success')) return async def open_server_directory(self, server: Server) -> None: try: os.startfile(server.path) # type: ignore except Exception: - console.print('[bold magenta]无法打开服务器目录。') + console.print(locale.trans_key('cannot-open-server-directory')) async def set_startup_command(self, index: int) -> None: - cmd = await promptInput('请输入命令,将在服务器启动前在服务器目录执行:') + cmd = await promptInput(locale.trans_key('command-execute-before-server-start-prompt-input')) await self.Workspace.modifyData(index, 'startup_cmd', cmd) - console.print('[bold green]命令设置成功。') + console.print(locale.trans_key('startup-cmd-set')) async def set_jvm_settings(self, index: int) -> None: - console.print('[white bold]请输入JVM参数(包含横杠,例如-Xms1G,可多个),将在服务器启动时添加至启动参数内\n默认已设置-Dfile.encoding=utf-8以及-Xmx') - jvm_setting = await promptInput('此为高级设置,若您不了解请勿随意填写:') + console.print(locale.trans_key('jvm-setting')) + jvm_setting = await promptInput(locale.trans_key('jvm-setting-prompt')) await self.Workspace.modifyData(index, 'jvm_setting', jvm_setting) - console.print('[bold green]JVM参数设置成功。') + console.print(locale.trans_key('jvm-setting-success')) async def set_autorun(self, server: Server) -> None: - if not await promptConfirm(f'确定要将 {server.name} 设为自动启动吗?'): return + if not await promptConfirm(locale.trans_key('set-to-auto-run-ask', servername = server.name)): return self.config.autorun = server.name self.config.save() - console.print('[bold green]自动启动设置成功,将在下次运行此软件时自动打开该服务器。') + console.print(locale.trans_key('set-to-auto-run-2')) async def export_start_script(self, server: Server) -> None: script_name = f'{server.name}.run.bat' if os.name == 'nt' else f'{server.name}.run.sh' with open(script_name, 'w') as f: f.write(await server.gen_run_command(self.Workspace.dir, export=True)) - console.print(f'[green]生成启动脚本成功({script_name})') + console.print(locale.trans_key('exported-start-script', script_name = script_name)) await asyncio.sleep(3) async def editConfig(self, server: Server) -> None: - console.print('[blue bold]读取特定配置索引:') configs = self.spconfigs - if not configs: - console.print('[bold magenta]特定配置索引读取失败,请检查网络连接。') - return editableConfigs = await self.get_editable_configs(configs, server) if not editableConfigs: - console.print('[bold magenta]没有可编辑的配置文件。') + console.print(locale.trans_key('no-specific-config-available')) return - await self.edit_selected_configs(editableConfigs, server) return async def get_editable_configs(self, configs, server: Server) -> list: @@ -248,7 +245,6 @@ async def get_editable_configs(self, configs, server: Server) -> list: with open(config_path, 'r', encoding='utf-8') as f: config = self.load_config_file(config_info, f) - console.print(f'Loaded conig file{config_info["name"]}.') if any(self.get_nested_value(config, key_info['key'].split('.')) is not None for key_info in config_info['keys']): editableConfigs.append((config_info,config)) return editableConfigs @@ -260,7 +256,7 @@ def load_config_file(self, config_info: dict, f) -> dict: return yaml.safe_load(f) return {} - def get_nested_value(self, config: dict, keys: list) -> dict | None: + def get_nested_value(self, config: dict, keys: list) -> Any: for key in keys: config = config.get(key, None) if config is None: @@ -274,8 +270,8 @@ def set_nested_value(self, config, keys, value): async def edit_selected_configs(self, editableConfigs, server) -> None: while True: selected_index = await promptSelect( - [f"{x[0]['name']} - {x[0]['description']}" for x in editableConfigs] + ['返回'], - '请选择要修改的配置文件:' + [f"{x[0]['name']} - {x[0]['description']}" for x in editableConfigs] + locale.trans_key(['return']), + locale.trans_key('specific-config-file-edit-select') ) if selected_index == len(editableConfigs): break @@ -288,8 +284,8 @@ async def edit_config_items(self, editConfig, config, server) -> None: editableKeys = [(key_info['key'], f"{key_info['name']} - {key_info['description']}") for key_info in editConfig['keys']] while True: editKeyIndex = await promptSelect( - [x[1] for x in editableKeys] + ['返回'], - '请选择要修改的配置项:' + [x[1] for x in editableKeys] + locale.trans_key(['return']), + locale.trans_key('specific-config-edit-select') ) if editKeyIndex == len(editableKeys): break @@ -297,10 +293,12 @@ async def edit_config_items(self, editConfig, config, server) -> None: key, _ = editableKeys[editKeyIndex] key_info: dict = editConfig['keys'][editKeyIndex] value = self.get_nested_value(config, key.split('.')) - console.print(f'[bold green]{key_info["name"]} - 当前值: {value}') + if not value: + return + console.print(locale.trans_key('specific-config-current-value',name=key_info['name'], value = value)) console.print(f'[bold white]Tips: {key_info["tips"]}') if key_info.get('danger', False): - console.print('[bold red]这是一个危险配置!修改前请三思!') + console.print(locale.trans_key('danger-config-warn')) editValue = await self.input_new_value(editConfig, key_info) self.set_nested_value(config, key.split('.'), editValue) self.save_config_file(editConfig, config, server.path) @@ -308,17 +306,17 @@ async def edit_config_items(self, editConfig, config, server) -> None: async def input_new_value(self, editConfig, key_info) -> str | int | bool | None: if key_info['type'] == "int": - key = await promptInput('请输入新值(整数):') + key = await promptInput(locale.trans_key('enter-new-value-int')) return key if editConfig['type'] == 'properties' else int(key) elif key_info['type'] == "str": - return await promptInput('请输入新值(字符串):') + return await promptInput(locale.trans_key('enter-new-value-str')) elif key_info['type'] == "bool": if editConfig['type'] == 'properties': - return 'true' if await promptConfirm('请选择新值:') else 'false' - return await promptConfirm('请选择新值:') + return 'true' if await promptConfirm(locale.trans_key('enter-new-value-bool')) else 'false' + return await promptConfirm(locale.trans_key('enter-new-value-bool')) elif key_info['type'] == "choice": - choice_index = await promptSelect(key_info['choices'], '请选择新值:') + choice_index = await promptSelect(key_info['choices'], locale.trans_key('enter-new-value-select')) return key_info['choices'][choice_index] return None @@ -337,57 +335,65 @@ def save_config_file(self, editConfig, config, server_path) -> bool: except Exception: return False async def delete(self) -> None: - console.rule('服务器删除') + console.rule(locale.trans_key('delete-server')) if not self.Workspace.workspaces: - console.print('没有服务器。') + console.print(locale.trans_key('no-such-server')) return - index = await promptSelect([x['name'] for x in self.Workspace.workspaces], '请选择要删除的服务器:') - if await promptConfirm('确定要删除吗?'): + index = await promptSelect([x['name'] for x in self.Workspace.workspaces], locale.trans_key('delete-server-prompt-select')) + if await promptConfirm(locale.trans_key('delete-server-prompt-confirm')): await self.Workspace.delete(index) return async def setting(self): - console.rule('设置') - index = await promptSelect(OPTIONS_SETTINGS, '设置:') + console.rule(locale.trans_key('settings')) + index = await promptSelect(OPTIONS_SETTINGS, locale.trans_key('settings')) settings_methods = { - 0: lambda: self.set_debug_mode(), - 1: lambda: self.set_mirror_priority(), - 2: lambda: self.set_run_on_startup(), + 0: lambda: self.set_language(), + 1: lambda: self.set_debug_mode(), + 2: lambda: self.set_mirror_priority(), + 3: lambda: self.set_run_on_startup(), len(OPTIONS_SETTINGS) - 1: lambda: self.do_nothing() } await settings_methods[index]() self.config.save() + async def set_language(self): + lang = await promptSelect(OPTIONS_LANGUAGE, locale.trans_key('select-language')) + locale.set_language(OPTIONS_LANGUAGE_CODE[lang]) + constants_init() + self.config.language = OPTIONS_LANGUAGE_CODE[lang] + self.config.save() + console.print(locale.trans_key('language-changed')) async def set_run_on_startup(self): if os.name == 'nt': #new feature: add to registry - reg_key = reg.OpenKey(AUTORUN_REG_HKEY, AUTORUN_REG_PATH, 0, reg.KEY_SET_VALUE) - query_reg_key = reg.OpenKey(AUTORUN_REG_HKEY, AUTORUN_REG_PATH, 0, reg.KEY_QUERY_VALUE) + reg_key = reg.OpenKey(AUTORUN_REG_HKEY, AUTORUN_REG_PATH, 0, reg.KEY_SET_VALUE) # type: ignore + query_reg_key = reg.OpenKey(AUTORUN_REG_HKEY, AUTORUN_REG_PATH, 0, reg.KEY_QUERY_VALUE) # type: ignore #check if HSL is already in the registry try: - reg.QueryValueEx(query_reg_key, HSL_NAME) - console.print('[bold green]Hikari Server Launcher 已在开机自启,无需重复设置。') - if await promptConfirm('是否移除开机自启设置?'): - reg.DeleteValue(reg_key, HSL_NAME) + reg.QueryValueEx(query_reg_key, HSL_NAME) # type: ignore + console.print(locale.trans_key('already-in-autorun-registry')) + if await promptConfirm(locale.trans_key('autorun-registry-remove-prompt-confirm')): + reg.DeleteValue(reg_key, HSL_NAME) # type: ignore return except FileNotFoundError: pass if not await promptConfirm( - '是否要将 Hikari Server Launcher 设为开机自启?' + locale.trans_key('autorun-registry-add-prompt-confirm') ): return exec_path = os.path.abspath(sys.argv[0]) - reg.SetValueEx(reg_key, HSL_NAME, 0, reg.REG_SZ, exec_path) + reg.SetValueEx(reg_key, HSL_NAME, 0, reg.REG_SZ, exec_path) # type: ignore else: - console.print('[bold magenta]当前系统不支持开机自启。') + console.print(locale.trans_key('autorun-not-supported-os')) return async def set_debug_mode(self): - self.config.debug = await promptConfirm('开启调试模式?') + self.config.debug = await promptConfirm(locale.trans_key('debug-mode-prompt-select')) async def set_mirror_priority(self): - self.config.use_mirror = await promptConfirm('是否使用镜像源优先?') + self.config.use_mirror = await promptConfirm(locale.trans_key('set-mirror-priority-prompt-select')) async def advanced_options(self): - index = await promptSelect(OPTIONS_ADVANCED, '高级选项:') + index = await promptSelect(OPTIONS_ADVANCED, locale.trans_key('advanced-settings')) advanced_methods = { len(OPTIONS_ADVANCED) - 1: lambda: self.do_nothing() @@ -396,13 +402,15 @@ async def advanced_options(self): async def mainMenu(self): while True: - console.rule(f'{HSL_NAME} [bold blue]v{str(self.version/10)}' + (' [white]- [bold red]Debug Mode' if self.config.debug else '')) - console.set_window_title(f'{HSL_NAME} v{str(self.version/10)}' + (' [white]- [bold red]Debug Mode' if self.config.debug else '')) - console.print(f'[bold blue]你正在运行{HSL_NAME} 版本号:[u]{self.version/10}[/u],次要版本:[u]{self.minor_version}[/u]') + title = locale.trans_key('title', version = str(self.version/10)) + (locale.trans_key('title-debug') if self.config.debug else '') + console.rule(title) + console.set_window_title(title) + console.print(locale.trans_key('version', HSL_NAME = HSL_NAME, version = str(self.version/10), minor_version = str(self.minor_version))) try: - index = await promptSelect(OPTIONS_MENU, '菜单:') + index = await promptSelect(OPTIONS_MENU, locale.trans_key('menu')) except (KeyboardInterrupt, asyncio.CancelledError): pass + index = 7 menu_methods: dict[int, Callable] = { 0: lambda: self.create(), @@ -416,67 +424,67 @@ async def mainMenu(self): } await menu_methods[index]() async def get_sponsor_list(self): - table = Table(title='赞助者',show_header=False) + table = Table(title=locale.trans_key('sponsor'),show_header=False) sponsor_list = get_sponsor_list() for sponsor in sponsor_list: table.add_row(f'[bold green]{sponsor}[/bold green]') table.add_section() - table.add_row('[bold blue]感谢以上人员对本项目的支持![/bold blue]') + table.add_row(locale.trans_key('sponsor-thanks')) console.print(table) async def autorun(self): server = await self.Workspace.getFromName(self.config.autorun) - console.print(f'[bold blue]将于三秒后启动 {server.name}。键入Ctrl+C(^C)可取消.') + console.print(locale.trans_key('server-auto-run-prompt',servername=server.name)) await asyncio.sleep(3) await server.run(self.Workspace.dir) exit() async def backups(self): - console.rule('备份管理') + console.rule(locale.trans_key('backup-management')) backup_methods: dict[int, Callable] = { 0: lambda: self.create_backup(), 1: lambda: self.restore_backup(), 2: lambda: self.delete_backup(), len(OPTIONS_BACKUPS) - 1: lambda: self.do_nothing() } - index = await promptSelect(OPTIONS_BACKUPS, '备份管理:') + index = await promptSelect(OPTIONS_BACKUPS, locale.trans_key('backup-management')) await backup_methods[index]() async def create_backup(self): servers = await self.Workspace.getAll() - server_index = await promptSelect([x.name for x in servers], '请选择要备份的服务器:') + server_index = await promptSelect([x.name for x in servers], locale.trans_key('backup-server-prompt-select')) server = servers[server_index] - with console.status(f'正在备份 {server.name}...'): + with console.status(locale.trans_key('backup-creating', servername=server.name)): backup_file = await self.Backup.backup_server(server) - print(f"{server.name} 的备份已保存至 {backup_file}") + print(locale.trans_key('backup-create-success', servername=server.name, backupname=backup_file)) return True async def restore_backup(self): servers = await self.Workspace.getAll() - server_index = await promptSelect([x.name for x in servers], '请选择要恢复备份的服务器:') + server_index = await promptSelect([x.name for x in servers], locale.trans_key('backup-restore-server-prompt-select')) server = servers[server_index] backups = await self.Backup.get_backup_list() if not backups: - console.print('[bold magenta]没有可用的备份。') + console.print(locale.trans_key('no-backup-available')) return - backup_index = await promptSelect(list(backups), '请选择要恢复的备份:') + backup_index = await promptSelect(list(backups), locale.trans_key('backup-restore-select')) backup_file = backups[backup_index] - with console.status(f'正在恢复 {server.name}...'): + with console.status(locale.trans_key('backup-restoring',backupname = backup_file)): await self.Backup.restore_backup(server, backup_file) - console.print(f"{server.name} 的备份已恢复。") + console.print(locale.trans_key('backup-restore-success', servername=server.name, backupname=backup_file)) return True async def delete_backup(self): backups = await self.Backup.get_backup_list() if not backups: - console.print('[bold magenta]没有可用的备份。') + console.print(locale.trans_key('no-backup-available')) return - backup_index = await promptSelect(list(backups), '请选择要删除的备份:') + backup_index = await promptSelect(list(backups), locale.trans_key('delete-backup-prompt-select')) backup_file = backups[backup_index] - if await promptConfirm(f'确定要删除 {backup_file} 吗?'): + if await promptConfirm(locale.trans_key('delete-backup-prompt-confirm', backupname=backup_file)): await self.Backup.delete_backup(backup_file) return True mainProgram = HSL_MAIN() async def main(): - cutask = asyncio.create_task(check_update()) + task = asyncio.create_task(check_update()) # isOutdated, new = mainProgram.flag_outdated, mainProgram.latest_version if mainProgram.config.first_run: await mainProgram.welcome() @@ -489,11 +497,10 @@ async def main(): except (KeyboardInterrupt, asyncio.CancelledError): mainProgram.config.autorun = '' mainProgram.config.save() - console.print('自动启动已取消并重置,如需再次启用请重新设置。') - await asyncio.sleep(1) - + console.print(locale.trans_key('autorun-canceled')) await mainProgram.mainMenu() - await cutask + await task + if __name__ == '__main__': try: @@ -501,8 +508,8 @@ async def main(): except SystemExit: pass except noneprompt.CancelledError: - console.print('[bold green]用户取消操作,已退出。') + console.print(locale.trans_key('user-cancel-operate')) except Exception as e: - console.print(f'[bold red]发生未知错误: {e}') + console.print(locale.trans_key('unknown-error-occur',e = str(e))) if mainProgram.config.debug: console.print_exception() \ No newline at end of file