From b7e36c996042620571fad4b1879b99cefd4584b4 Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 14:47:15 +0800 Subject: [PATCH 1/7] Update dev scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新开发脚本: - 增加对 poetry build 构建的异常处理; - 修复路径常量中获取项目根路径的方法,排除运行脚本的工作目录不同带来的干扰; - 添加两个关于构建 PySide 翻译工作流的函数 `gen_ts()` 与 `gen_qm()`; - 将 RCC 资源编译函数 `compile_resources()` 重构至新的模块中; --- dev_scripts/build.py | 47 ++++------------ dev_scripts/path_constants.py | 9 +-- dev_scripts/pyside6_tools_script.py | 85 +++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 39 deletions(-) create mode 100644 dev_scripts/pyside6_tools_script.py diff --git a/dev_scripts/build.py b/dev_scripts/build.py index 85c1231..65831f1 100644 --- a/dev_scripts/build.py +++ b/dev_scripts/build.py @@ -11,12 +11,7 @@ from dev_scripts.check_funcs import ( check_version_num, ) from dev_scripts.clear_cache import clear_pycache, clear_pyinstaller_dist -from dev_scripts.path_constants import ( - PROJECT_ROOT, - README_FILE_LIST, - RESOURCES_PATH, - SRC_PATH, -) +from dev_scripts.path_constants import PROJECT_ROOT, README_FILE_LIST, SRC_PATH def process_md_images(md_file_list: list[Path]) -> None: @@ -46,30 +41,6 @@ def process_md_images(md_file_list: list[Path]) -> None: # FIXME 会在文件尾部多出来莫名其妙的行 -def compile_resources() -> int: - """调用 RCC 工具编译静态资源 - - :return: rcc 进程返回码 - """ - - compiled_file_path = RESOURCES_PATH / "COMPILED_RESOURCES.py" - qrc_file_path = RESOURCES_PATH / "resources.qrc" - cmd = [ - "pyside6-rcc", - "-o", - str(compiled_file_path.absolute()), - str(qrc_file_path.absolute()), - ] - try: - result = subprocess.run(cmd) - except subprocess.SubprocessError as e: - print(f"RCC编译进程错误:{e}") - raise e - else: - print(f"已完成静态资源文件编译,RCC返回码:{result.returncode}。") - return result.returncode - - def export_requirements() -> int: """将项目依赖项导出至 requirements.txt 中 @@ -109,12 +80,18 @@ def build_py2exe_gui() -> None: print(f"mypy 检查完毕,返回码:{check_mypy()}。") # 正式构建 - subprocess.run(["poetry", "build"]) # TODO 处理异常与返回值 - - # 清理 - process_md_images(README_FILE_LIST) + try: + result = subprocess.run(["poetry", "build"], check=True) + except subprocess.SubprocessError as e: + print(f"Poetry build 失败:{e}") + raise + else: + print(f"Poetry build 完毕,返回码:{result.returncode}。") + finally: + # 清理 + process_md_images(README_FILE_LIST) else: - print("构建失败,有未通过的检查项") + print("有未通过的检查项,不进行构建") if __name__ == "__main__": diff --git a/dev_scripts/path_constants.py b/dev_scripts/path_constants.py index 3a50770..891b3b5 100644 --- a/dev_scripts/path_constants.py +++ b/dev_scripts/path_constants.py @@ -1,15 +1,16 @@ -""" -开发脚本中使用的相对路径常量 +"""开发脚本中使用的相对路径常量 + 所有脚本应以项目根目录为工作目录运行 """ from pathlib import Path -PROJECT_ROOT = Path("../") # 项目根目录 +PROJECT_ROOT = Path(__file__).parent.parent # 项目根目录 SRC_PATH = PROJECT_ROOT / "src" # 源码目录 -SRC_PKG_PATH = SRC_PATH / "py2exe_gui" +SRC_PKG_PATH = SRC_PATH / "py2exe_gui" # 包目录 RESOURCES_PATH = SRC_PKG_PATH / "Resources" # 静态资源文件目录 COMPILED_RESOURCES = RESOURCES_PATH / "COMPILED_RESOURCES.py" # 编译静态资源文件 +WIDGETS_PATH = SRC_PKG_PATH / "Widgets" # 控件目录 README_FILE_LIST = [ PROJECT_ROOT / "README.md", PROJECT_ROOT / "README_zh.md", diff --git a/dev_scripts/pyside6_tools_script.py b/dev_scripts/pyside6_tools_script.py new file mode 100644 index 0000000..df4bdbe --- /dev/null +++ b/dev_scripts/pyside6_tools_script.py @@ -0,0 +1,85 @@ +"""开发脚本,便于调用 PySide6 提供的各种工具程序 +""" + +import subprocess + +from dev_scripts.path_constants import ( + COMPILED_RESOURCES, + PROJECT_ROOT, + RESOURCES_PATH, + SRC_PKG_PATH, + WIDGETS_PATH, +) + + +def compile_resources() -> int: + """调用 RCC 工具编译静态资源 + + :return: rcc 进程返回码 + """ + + compiled_file_path = COMPILED_RESOURCES + qrc_file_path = RESOURCES_PATH / "resources.qrc" + cmd = [ + "pyside6-rcc", + "-o", + compiled_file_path, + qrc_file_path, + ] + + try: + result = subprocess.run(cmd, cwd=PROJECT_ROOT, check=True) + except subprocess.SubprocessError as e: + print(f"RCC 编译进程错误:{e}") + raise e + else: + print(f"已完成静态资源文件编译,RCC 返回码:{result.returncode}。") + return result.returncode + + +def gen_ts(lang: str = "zh_CN") -> int: + """调用 lupdate 工具分析源码,生成 .ts 文本翻译文件 + + :param lang: 目标翻译语言代码 + :return: lupdate 返回码 + """ + + source = [*list(WIDGETS_PATH.glob("**/*.py")), SRC_PKG_PATH / "__main__.py"] + target = RESOURCES_PATH / "i18n" / f"{lang.replace('-', '_')}.ts" + cmd = ["pyside6-lupdate", *source, "-ts", target] + + try: + result = subprocess.run(cmd, cwd=PROJECT_ROOT, check=True) + except subprocess.SubprocessError as e: + print(f"lupdate 进程错误:{e}") + raise + else: + print(f"已完成文本翻译文件生成,lupdate 返回码:{result.returncode}。") + return result.returncode + + +def gen_qm(lang: str = "zh_CN") -> int: + """调用 lrelease 工具编译.ts 文本翻译文件 + + :param lang: 目标翻译语言代码 + :return: lrelease 返回码 + """ + + source = RESOURCES_PATH / "i18n" / f"{lang.replace('-', '_')}.ts" + target = RESOURCES_PATH / "i18n" / f"{lang.replace('-', '_')}.qm" + cmd = ["pyside6-lrelease", source, "-qm", target] + + try: + result = subprocess.run(cmd, cwd=PROJECT_ROOT, check=True) + except subprocess.SubprocessError as e: + print(f"lrelease 进程错误:{e}") + raise + else: + print(f"已完成文本翻译文件编译,lrelease 返回码:{result.returncode}。") + return result.returncode + + +if __name__ == "__main__": + # compile_resources() + gen_ts("zh_CN") + # gen_qm("zh_CN") From b099b9462513b5de3de313c99a0e448272d6621e Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 15:39:39 +0800 Subject: [PATCH 2/7] Add `QObjTr` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加 `QObjTr` 类,为实现界面翻译做准备; --- src/py2exe_gui/Utilities/__init__.py | 1 + src/py2exe_gui/Utilities/qobject_tr.py | 48 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/py2exe_gui/Utilities/qobject_tr.py diff --git a/src/py2exe_gui/Utilities/__init__.py b/src/py2exe_gui/Utilities/__init__.py index 41c5ba1..e769916 100644 --- a/src/py2exe_gui/Utilities/__init__.py +++ b/src/py2exe_gui/Utilities/__init__.py @@ -6,3 +6,4 @@ from .open_qfile import QtFileOpen from .platform_specifc_funcs import get_sys_python, open_dir_in_explorer from .python_env import ALL_PY_ENVs, PyEnv +from .qobject_tr import QObjTr diff --git a/src/py2exe_gui/Utilities/qobject_tr.py b/src/py2exe_gui/Utilities/qobject_tr.py new file mode 100644 index 0000000..b182e78 --- /dev/null +++ b/src/py2exe_gui/Utilities/qobject_tr.py @@ -0,0 +1,48 @@ +# Licensed under the GPLv3 License: https://www.gnu.org/licenses/gpl-3.0.html +# For details: https://github.com/muziing/Py2exe-GUI/blob/main/README.md#license + +"""自实现 QObject.tr() 方法,使得 PyCharm 等静态检查工具不再报错。 +""" + +__all__ = [ + "QObjTr", +] + +from typing import Optional + +from PySide6.QtCore import QCoreApplication + + +class QObjTr: + """利用 Python 多继承机制,为任何需要Qt翻译的类提供类方法 `tr()`。 + + 只能通过类名调用(形如 CLASSNAME.tr()),不能通过实例调用(形如 self.tr())!对于有继承关系的控件, + 通过子类的实例调用 tr() 只能得到子类方法中涉及的字符串,而在父类中涉及的字符串都会丢失。 + + 假设有名为 MainWindow 的类,继承自 PySide6.QtWidgets.QMainWindow,需要在其实例属性中设置带翻译的文本, + 那么应当这样做: + + 1. 将 MainWindow 同时继承自 QObjTr 和 QMainWindow:`class MainWindow(QObjTr, QMainWindow): ...` + 2. 在实例属性中,凡需要翻译的字符串,使用 MainWindow.tr()包裹,比如:`MainWindow.tr("&File")` + """ + + @classmethod + def tr(cls, msg: str, disambiguation: Optional[str] = None, n: int = -1) -> str: + """Returns a translated version of `msg` + + **Note: Can only be used as `CLASSNAME.tr()`, not as `self.tr()`!** + + **注意:只能通过类名调用,不能通过实例调用!否则会在涉及到继承的控件中失效。** + + Wrap `QCoreApplication.translate()` to `QObject.tr()`.This will make PyCharm + happy. Now you can use `CLASSNAME.tr()` freely. For more details, see: + + + + :param msg: Origin messages + :param disambiguation: Disambiguate identical text, see Qt Docs for details + :param n: Handle plural forms, see Qt Docs for details + :return: Translated messages + """ + + return QCoreApplication.translate(cls.__name__, msg, disambiguation, n) From cea7f5ba2a6108cabb76623fee838db1a60bb607 Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 20:36:48 +0800 Subject: [PATCH 3/7] Update resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新静态资源文件: - 更新「关于」文档中的版权信息; - 将 Text 中的 zh 改为更准确的 zh_CN; - qrc 中添加 `i18n/zh_CN.qm`,为实现界面翻译做准备; --- src/py2exe_gui/Resources/Texts/About_en.md | 2 +- .../Resources/Texts/{About_zh.md => About_zh_CN.md} | 2 +- ...er_options_zh.yaml => pyinstaller_options_zh_CN.yaml} | 0 src/py2exe_gui/Resources/resources.qrc | 9 ++++++--- 4 files changed, 8 insertions(+), 5 deletions(-) rename src/py2exe_gui/Resources/Texts/{About_zh.md => About_zh_CN.md} (90%) rename src/py2exe_gui/Resources/Texts/{pyinstaller_options_zh.yaml => pyinstaller_options_zh_CN.yaml} (100%) diff --git a/src/py2exe_gui/Resources/Texts/About_en.md b/src/py2exe_gui/Resources/Texts/About_en.md index a8a5efe..fb8c4b2 100644 --- a/src/py2exe_gui/Resources/Texts/About_en.md +++ b/src/py2exe_gui/Resources/Texts/About_en.md @@ -6,4 +6,4 @@ This program is **open source software**: all source code is hosted on [GitHub]( This program is **free software**: you can redistribute it and/or modify it under the terms of the [GNU General Public License](https://github.com/muziing/Py2exe-GUI/blob/main/README.md#license) as published by the Free Software Foundation, either version 3 of the License, or any later version. -Copyright © 2022-2023 [Muzing \](https://muzing.top/about) +Copyright © 2022-2024 [Muzing \](https://muzing.top/about) diff --git a/src/py2exe_gui/Resources/Texts/About_zh.md b/src/py2exe_gui/Resources/Texts/About_zh_CN.md similarity index 90% rename from src/py2exe_gui/Resources/Texts/About_zh.md rename to src/py2exe_gui/Resources/Texts/About_zh_CN.md index 892ceb4..56eed36 100644 --- a/src/py2exe_gui/Resources/Texts/About_zh.md +++ b/src/py2exe_gui/Resources/Texts/About_zh_CN.md @@ -6,4 +6,4 @@ Py2exe-GUI 是一个基于 [PyInstaller](https://pyinstaller.org/) 的图形化 本程序为**自由软件**:在自由软件联盟发布的 [*GNU 通用公共许可协议(第3版或更新的版本)*](https://github.com/muziing/Py2exe-GUI/blob/main/README.md#license) 的约束下,你可以对其进行再发布和/或修改。 -版权所有 © 2022-2023 [Muzing \](https://muzing.top/about) +版权所有 © 2022-2024 [Muzing \](https://muzing.top/about) diff --git a/src/py2exe_gui/Resources/Texts/pyinstaller_options_zh.yaml b/src/py2exe_gui/Resources/Texts/pyinstaller_options_zh_CN.yaml similarity index 100% rename from src/py2exe_gui/Resources/Texts/pyinstaller_options_zh.yaml rename to src/py2exe_gui/Resources/Texts/pyinstaller_options_zh_CN.yaml diff --git a/src/py2exe_gui/Resources/resources.qrc b/src/py2exe_gui/Resources/resources.qrc index d8c3185..fae4b4f 100644 --- a/src/py2exe_gui/Resources/resources.qrc +++ b/src/py2exe_gui/Resources/resources.qrc @@ -8,9 +8,12 @@ Icons/poetry-icon_128px.png Icons/conda-icon_72px.png - - Texts/About_zh.md - Texts/pyinstaller_options_zh.yaml + + i18n/zh_CN.qm + + + Texts/About_zh_CN.md + Texts/pyinstaller_options_zh_CN.yaml Texts/About_en.md From c42fbaecc917d813ddffe6a128bd7ed3090ed29c Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 20:46:18 +0800 Subject: [PATCH 4/7] New feature: i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 初步实现界面控件的国际化翻译功能; 初步完成界面的全英文翻译; --- src/Py2exe-GUI.py | 11 +- .../Resources/COMPILED_RESOURCES.py | 534 ++++++++++++++++- src/py2exe_gui/Resources/i18n/zh_CN.qm | Bin 0 -> 7518 bytes src/py2exe_gui/Resources/i18n/zh_CN.ts | 540 ++++++++++++++++++ src/py2exe_gui/Widgets/add_data_widget.py | 29 +- src/py2exe_gui/Widgets/arguments_browser.py | 12 +- src/py2exe_gui/Widgets/center_widget.py | 144 +++-- src/py2exe_gui/Widgets/dialog_widgets.py | 89 ++- src/py2exe_gui/Widgets/main_window.py | 31 +- .../Widgets/multi_item_edit_widget.py | 18 +- .../Widgets/pyinstaller_option_widget.py | 18 +- src/py2exe_gui/Widgets/subprocess_widget.py | 35 +- src/py2exe_gui/__main__.py | 24 +- 13 files changed, 1300 insertions(+), 185 deletions(-) create mode 100644 src/py2exe_gui/Resources/i18n/zh_CN.qm create mode 100644 src/py2exe_gui/Resources/i18n/zh_CN.ts diff --git a/src/Py2exe-GUI.py b/src/Py2exe-GUI.py index e2c6bd1..e8bb244 100644 --- a/src/Py2exe-GUI.py +++ b/src/Py2exe-GUI.py @@ -11,13 +11,6 @@ Py2exe-GUI 启动方式: python -m py2exe_gui """ -import sys +from py2exe_gui.__main__ import main -from PySide6.QtWidgets import QApplication - -from py2exe_gui.__main__ import MainApp - -app = QApplication(sys.argv) -window = MainApp() -window.show() -sys.exit(app.exec()) +main() diff --git a/src/py2exe_gui/Resources/COMPILED_RESOURCES.py b/src/py2exe_gui/Resources/COMPILED_RESOURCES.py index c031515..fec0379 100644 --- a/src/py2exe_gui/Resources/COMPILED_RESOURCES.py +++ b/src/py2exe_gui/Resources/COMPILED_RESOURCES.py @@ -6,6 +6,478 @@ from PySide6 import QtCore qt_resource_data = b"\ +\x00\x00\x1d^\ +<\ +\xb8d\x18\xca\xef\x9c\x95\xcd!\x1c\xbf`\xa1\xbd\xdd\xa7\ +\x00\x00\x00\x05zh_CNB\x00\x00\x03\x00\x00\x00\ ++;\x00\x00\x01\x82\x00\x00+;\x00\x00\x11\xbd\x00\x02\ +\xb4\xc7\x00\x00\x01P\x00\x02\xb4\xc7\x00\x00\x11\x85\x00\x04\ +\xa6y\x00\x00\x02&\x00\x05H5\x00\x00\x12w\x00\x05\ +f\xbe\x00\x00\x0b\xc1\x00\x05f\xbe\x00\x00\x13\xa8\x00\x10\ +\xcan\x00\x00\x04h\x00\x13U\xee\x00\x00\x0a;\x00*\ +\xcf\x04\x00\x00\x0eN\x00*\xd0%\x00\x00\x0e~\x00*\ +\xec0\x00\x00\x0e\xae\x00G\x96\xc4\x00\x00\x00\x00\x00I\ +l\x0e\x00\x00\x03L\x00J+~\x00\x00\x05\x02\x00J\ +6\x95\x00\x00\x14\xf0\x00Kdq\x00\x00\x15\x1b\x00L\ +\x99b\x00\x00\x05\xa0\x00L\x99b\x00\x00\x17\xc3\x01*\ +\xbc^\x00\x00\x04\xb4\x01\x8a\xa33\x00\x00\x11\xf4\x01\xdb\ +\xb2\x8f\x00\x00\x07B\x01\xf3(\xfe\x00\x00\x15\xfd\x02$\ +\x06\xee\x00\x00\x19E\x02TK\xfa\x00\x00\x03\x98\x02o\ +\xeej\x00\x00\x17X\x02r\xf7\xfe\x00\x00\x05,\x02\xa7\ +\x96\xc4\x00\x00\x0e\x1d\x02\xa7\xdeU\x00\x00\x0f\x81\x02\xb6\ +\xee\xd3\x00\x00\x10\x7f\x03\x8aRY\x00\x00\x0bb\x03\xbc\ +\xb9r\x00\x00\x01\xe6\x04\x13\xa2*\x00\x00\x08\x93\x04a\ +\xc9p\x00\x00\x18T\x04t\xcbE\x00\x00\x06G\x04\x98\ +I\xbc\x00\x00\x0b\x07\x04\x98I\xbc\x00\x00\x13|\x04\x98\ +I\xbc\x00\x00\x14\xc4\x04\x99n\x95\x00\x00\x03\xcc\x04\x99\ +n\x95\x00\x00\x17\x92\x04\xcfv\x94\x00\x00\x02S\x04\xd1\ +N\xb2\x00\x00\x06~\x05g\xb0^\x00\x00\x13\x08\x05\x8b\ +{\xbe\x00\x00\x0d\xf5\x06\x04\x81\x85\x00\x00\x0b1\x06L\ +\xc5c\x00\x00\x01\xb3\x06\x5c\xc6\xd5\x00\x00\x14\x19\x06m\ +\x81M\x00\x00\x0fF\x06\xc6l\x14\x00\x00\x0f\x12\x07$\ +\xfe\xde\x00\x00\x0a\xdb\x07?\xfd\xfe\x00\x00\x18\xdf\x07\xca\ +r\x11\x00\x00\x09x\x08\x05\x1f\xb0\x00\x00\x06\xfd\x08\xa1\ +\x96\xd9\x00\x00\x0cm\x09\x07[\x85\x00\x00\x0f\xd4\x09\x08\ +b\x8a\x00\x00\x08X\x09 \x93N\x00\x00\x10+\x09(\ +n\x13\x00\x00\x02\xc1\x09<\x5c\x83\x00\x00\x10\xd9\x09M\ +g\xfe\x00\x00\x12\xce\x09pU\x99\x00\x00\x14X\x09\xc4\ +\xb21\x00\x00\x03\xf7\x09\xd8%U\x00\x00\x0c-\x09\xf2\ +5X\x00\x00\x08\x05\x0a\x0f\xec\x8e\x00\x00\x00&\x0a\x1e\ +\xda\xd4\x00\x00\x05\xca\x0ae\xa8\xe3\x00\x00\x15L\x0a\x98\ +I\x9c\x00\x00\x00\xe6\x0a\x98I\x9c\x00\x00\x11\x0f\x0a\xac\ +,\x85\x00\x00\x01\x1b\x0a\xac,\x85\x00\x00\x11J\x0a\xb8\ +\xc2\xa1\x00\x00\x08\xe3\x0a\xebf\x09\x00\x00\x0a\x9e\x0a\xeb\ +f\x09\x00\x00\x0b\xe9\x0a\xebf\x09\x00\x00\x13=\x0b\x05\ +MN\x00\x00\x06\x00\x0b\xab\x22\xae\x00\x00\x02\xfc\x0b\xd4\ +\xbf\xa1\x00\x00\x16\xa2\x0b\xddZ\xc5\x00\x00\x00\xa8\x0b\xe1\ +gZ\x00\x00\x16\xf4\x0c\x05\x06\xc4\x00\x00\x15\xc6\x0c\x0a\ +\xbe\xf5\x00\x00\x04%\x0c\x1b\xd1\xa1\x00\x00\x19\xb1\x0cP\ +\xa0*\x00\x00\x06\xb7\x0c~`9\x00\x00\x0d\x87\x0c\xa8\ +?b\x00\x00\x0c\xc6\x0c\xba\xefs\x00\x00\x0e\xde\x0c\xc9\ +\xa0\x0e\x00\x00\x12\xa1\x0d\x89P\x17\x00\x00\x0a\x0f\x0e\x01\ ++e\x00\x00\x18\x9f\x0ef\xfa\x7f\x00\x00\x17\xf3\x0e\x81\ +2\x09\x00\x00\x0d\x13\x0f\xc0\xd03\x00\x00\x02\x82\x0f\xcf\ +\xb2#\x00\x00\x129\x0f\xe8\x01e\x00\x00\x13\xd2i\x00\ +\x00\x1a:\x03\x00\x00\x00\x04QsN\x8e\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x05About\x07\x00\x00\x00\x08\ +AboutDlg\x01\x03\x00\x00\x00(e\xe0\ +l\xd5bS_\x00QsN\x8ee\x87hc\xff\x0c\ +\x8b\xf7\x5c\x1d\x8b\xd5\x91\xcde\xb0\x83\xb7S\xd6g,\ +z\x0b^\x8f0\x02\x08\x00\x00\x00\x00\x06\x00\x00\x00=\ +Can't open the A\ +bout document, t\ +ry to reinstall \ +this program.\x07\x00\x00\ +\x00\x08AboutDlg\x01\x03\x00\x00\x00\x10\ +mO\x89\xc8e\x87N\xf6\x00(\x00&\x00B\x00)\ +\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0c&Brows\ +e File\x07\x00\x00\x00\x0dAddDa\ +taWindow\x01\x03\x00\x00\x00\x0cS\xd6\ +m\x88\x00(\x00&\x00C\x00)\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x07&Cancel\x07\x00\x00\x00\x0d\ +AddDataWindow\x01\x03\x00\ +\x00\x00\x0cR \x96d\x00(\x00&\x00D\x00)\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x07&Delete\ +\x07\x00\x00\x00\x0dAddDataWind\ +ow\x01\x03\x00\x00\x00\x0ce\xb0^\xfa\x00(\x00&\ +\x00N\x00)\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04&N\ +ew\x07\x00\x00\x00\x0dAddDataWi\ +ndow\x01\x03\x00\x00\x00\x0cxn\x8b\xa4\x00(\ +\x00&\x00O\x00)\x08\x00\x00\x00\x00\x06\x00\x00\x00\x03\ +&OK\x07\x00\x00\x00\x0dAddDataW\ +indow\x01\x03\x00\x00\x00\x08m\xfbR\xa0e\ +\x87N\xf6\x08\x00\x00\x00\x00\x06\x00\x00\x00\x09Add\ + Files\x07\x00\x00\x00\x0dAddDa\ +taWindow\x01\x03\x00\x00\x00\x10mO\ +\x89\xc8v\xee_U\x00(\x00&\x00F\x00)\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x0eBrowse &\ +Folder\x07\x00\x00\x00\x0dAddDa\ +taWindow\x01\x03\x00\x00\x00\x04Y\x0d\ +R6\x08\x00\x00\x00\x00\x06\x00\x00\x00\x04Copy\ +\x07\x00\x00\x00\x10ArgumentsBr\ +owser\x01\x03\x00\x00\x00\x04[\xfcQ\xfa\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x06Export\x07\ +\x00\x00\x00\x10ArgumentsBro\ +wser\x01\x03\x00\x00\x00\x0em\xfbR\xa0N\x8c\ +\x8f\xdbR6e\x87N\xf6\x08\x00\x00\x00\x00\x06\x00\x00\ +\x00\x10Add Binary Fil\ +es\x07\x00\x00\x00\x0cCenterWid\ +get\x01\x03\x00\x00\x00\x0cm\xfbR\xa0epc\ +ne\x87N\xf6\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0eA\ +dd Data Files\x07\x00\x00\ +\x00\x0cCenterWidget\x01\x03\ +\x00\x00\x00\x16m\xfbR\xa0N\x8c\x8f\xdbR6e\x87\ +N\xf6]\xf2f\xf4e\xb00\x02\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x19Add binary f\ +iles updated.\x07\x00\x00\ +\x00\x0cCenterWidget\x01\x03\ +\x00\x00\x00\x14m\xfbR\xa0epcne\x87N\xf6\ +]\xf2f\xf4e\xb00\x02\x08\x00\x00\x00\x00\x06\x00\x00\ +\x00\x17Add data files\ + updated.\x07\x00\x00\x00\x0cCe\ +nterWidget\x01\x03\x00\x00\x00\x0a\ +\x98yv\xeeT\x0dy\xf0\xff\x1a\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x09App Name:\x07\x00\x00\ +\x00\x0cCenterWidget\x01\x03\ +\x00\x00\x00\x04mO\x89\xc8\x08\x00\x00\x00\x00\x06\x00\x00\ +\x00\x06Browse\x07\x00\x00\x00\x0cCen\ +terWidget\x01\x03\x00\x00\x00\x06b\ +SS\x05\xff\x01\x08\x00\x00\x00\x00\x06\x00\x00\x00\x07B\ +undle!\x07\x00\x00\x00\x0cCente\ +rWidget\x01\x03\x00\x00\x00\x12bSS\ +\x05v\x84^\x94u(z\x0b^\x8fT\x0dy\xf0\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x10Bundled\ + app name\x07\x00\x00\x00\x0cCe\ +nterWidget\x01\x03\x00\x00\x00\x14\ +\x5c\x06bSS\x05\x81\xf3SUN*e\x87N\xf6\ +N-0\x02\x08\x00\x00\x00\x00\x06\x00\x00\x00\x17Bu\ +ndling into one \ +file.\x07\x00\x00\x00\x0cCenter\ +Widget\x01\x03\x00\x00\x00\x14\x5c\x06bS\ +S\x05\x81\xf3SUN*v\xee_UN-0\x02\ +\x08\x00\x00\x00\x00\x06\x00\x00\x00\x19Bundli\ +ng into one fold\ +er.\x07\x00\x00\x00\x0cCenterWi\ +dget\x01\x03\x00\x00\x00\x04n\x05t\x06\x08\x00\ +\x00\x00\x00\x06\x00\x00\x00\x05Clean\x07\x00\x00\ +\x00\x0cCenterWidget\x01\x03\ +\x00\x00\x00\x1cg\x84^\xfaRM\x5c\x06n\x05\x96d\ +\x7f\x13[XN\x0eN4e\xf6v\xee_U0\x02\ +\x08\x00\x00\x00\x00\x06\x00\x00\x007Clean \ +cache and remove\ + temporary files\ + before building\ +.\x07\x00\x00\x00\x0cCenterWidg\ +et\x01\x03\x00\x00\x00\x04\x95\x19\x8b\xef\x08\x00\x00\x00\ +\x00\x06\x00\x00\x00\x05Error\x07\x00\x00\x00\x0c\ +CenterWidget\x01\x03\x00\x00\ +\x00\x08\x96\x90_\x0f[\xfcQe\x08\x00\x00\x00\x00\x06\ +\x00\x00\x00\x0dHidden Impor\ +t\x07\x00\x00\x00\x0cCenterWidg\ +et\x01\x03\x00\x00\x00\x10\x96\x90_\x0f[\xfcQe\ +]\xf2f\xf4e\xb00\x02\x08\x00\x00\x00\x00\x06\x00\x00\ +\x00\x16Hidden import \ +updated.\x07\x00\x00\x00\x0cCen\ +terWidget\x01\x03\x00\x00\x00\x0eb\ +SS\x05\x81\xf3SUN*e\x87N\xf6\x08\x00\x00\ +\x00\x00\x06\x00\x00\x00\x08One File\x07\ +\x00\x00\x00\x0cCenterWidget\ +\x01\x03\x00\x00\x00\x0ebSS\x05\x81\xf3SUN*\ +v\xee_U\x08\x00\x00\x00\x00\x06\x00\x00\x00\x0aOn\ +e Folder\x07\x00\x00\x00\x0cCen\ +terWidget\x01\x03\x00\x00\x00\x10S\ +Uv\xee_U\x00/SUe\x87N\xf6\xff\x1a\x08\ +\x00\x00\x00\x00\x06\x00\x00\x00\x15One Fol\ +der/ One File:\x07\x00\ +\x00\x00\x0cCenterWidget\x01\ +\x03\x00\x00\x00\x10bS_\x00\x81\x1ag,\x8d\xef_\ +\x84\xff\x1a\x00 \x08\x00\x00\x00\x00\x06\x00\x00\x00\x14O\ +pened script pat\ +h: \x07\x00\x00\x00\x0cCenterWi\ +dget\x01\x03\x00\x00\x00NW(\x8b\xe5\x00 \ +\x00P\x00y\x00t\x00h\x00o\x00n\x00 s\xaf\ +X\x83N-O](https://muzi\ @@ -3726,7 +4198,7 @@ e) \xe7\x9a\x84\xe7\xba\xa6\xe6\x9d\x9f\xe4\xb8\x8b\xef\ \xb6\xe8\xbf\x9b\xe8\xa1\x8c\xe5\x86\x8d\xe5\x8f\x91\xe5\xb8\x83\ \xe5\x92\x8c/\xe6\x88\x96\xe4\xbf\xae\xe6\x94\xb9\xe3\x80\x82\ \x0a\x0a\xe7\x89\x88\xe6\x9d\x83\xe6\x89\x80\xe6\x9c\x89 \xc2\ -\xa9 2022-2023 [Mu\ +\xa9 2022-2024 [Mu\ zing \x5c](h\ ttps://muzing.to\ @@ -3742,6 +4214,14 @@ qt_resource_name = b"\ \x00O\xa6S\ \x00I\ \x00c\x00o\x00n\x00s\ +\x00\x04\ +\x00\x06\xc4\xee\ +\x00i\ +\x001\x008\x00n\ +\x00\x08\ +\x0e8\x05}\ +\x00z\ +\x00h\x00_\x00C\x00N\x00.\x00q\x00m\ \x00\x14\ \x01O\xad\x98\ \x00P\ @@ -3779,32 +4259,36 @@ qt_resource_name = b"\ " qt_resource_struct = b"\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x10\x00\x02\x00\x00\x00\x06\x00\x00\x00\x07\ +\x00\x00\x00 \x00\x02\x00\x00\x00\x01\x00\x00\x00\x0e\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x03\ +\x00\x00\x00\x10\x00\x02\x00\x00\x00\x06\x00\x00\x00\x08\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xc6\x00\x00\x00\x00\x00K\x00\x00\x80\xaa\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xac\ -\x00\x00\x00\xc6\x00\x00\x00\x00\x00:\x00\x00\xb2A\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xac\ -\x00\x00\x00\xf2\x00\x00\x00\x00\x00K\x00\x00\xe1\x9d\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xac\ -\x00\x00\x00\xf2\x00\x00\x00\x00\x00:\x00\x00\xe4\xb0\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xac\ -\x00\x00\x00\x9a\x00\x00\x00\x00\x00\x01\x00\x00f\x93\ -\x00\x00\x01\x8c\xbd[\xc8Z\ -\x00\x00\x00 \x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xaa\ -\x00\x00\x00l\x00\x00\x00\x00\x00\x01\x00\x00\x19\xb6\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xab\ -\x00\x00\x00\xaa\x00\x00\x00\x00\x00\x01\x00\x00w_\ -\x00\x00\x01\x8c\xb3\xa0\x98\xeb\ -\x00\x00\x00~\x00\x00\x00\x00\x00\x01\x00\x00<\x13\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xab\ -\x00\x00\x00N\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xfd\ -\x00\x00\x01\x8c\x9a\xb7\xd3\xaa\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x04\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\xea\x00\x00\x00\x00\x00K\x00\x00\x9e\x0c\ +\x00\x00\x01\x8c\x9fP\xce\x07\ +\x00\x00\x00\xea\x00\x00\x002\x00:\x00\x00\xcf\xa3\ +\x00\x00\x01\x8c\x9fP\xce\x07\ +\x00\x00\x01\x16\x00\x00\x00\x00\x00K\x00\x00\xfe\xff\ +\x00\x00\x01\x8c\xd4u\x1b\xc8\ +\x00\x00\x01\x16\x00\x00\x002\x00:\x00\x01\x02\x12\ +\x00\x00\x01\x8c\xd4u\x1b\xcf\ +\x00\x00\x00\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x83\xf5\ +\x00\x00\x01\x8c\xbd\xfa\xbd\x88\ +\x00\x00\x00D\x00\x00\x00\x00\x00\x01\x00\x00\x1db\ +\x00\x00\x01\x8b\xbd\x8a$\x9a\ +\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x007\x18\ +\x00\x00\x01\x8cz\xef\xd4\x05\ +\x00\x00\x00\xce\x00\x00\x00\x00\x00\x01\x00\x00\x94\xc1\ +\x00\x00\x01\x8c\xb3\x93\xcb\xe3\ +\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x01\x00\x00Yu\ +\x00\x00\x01\x8c\xbd\xfa\xbd\x88\ +\x00\x00\x00r\x00\x00\x00\x00\x00\x01\x00\x00*_\ +\x00\x00\x01\x8b\xbd\x8a$\x9a\ +\x00\x00\x00.\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x8c\xd3\xb8s\xe7\ " diff --git a/src/py2exe_gui/Resources/i18n/zh_CN.qm b/src/py2exe_gui/Resources/i18n/zh_CN.qm new file mode 100644 index 0000000000000000000000000000000000000000..97675eb0b9733f6772e283991e15ad8448cdfeea GIT binary patch literal 7518 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 + + + + AboutDlg + + + About + 关于 + + + + Can't open the About document, try to reinstall this program. + 无法打开关于文档,请尝试重新获取本程序。 + + + + AddDataWindow + + + Add Files + 添加文件 + + + + &New + 新建(&N) + + + + &Browse File + 浏览文件(&B) + + + + Browse &Folder + 浏览目录(&F) + + + + &Delete + 删除(&D) + + + + &OK + 确认(&O) + + + + &Cancel + 取消(&C) + + + + ArgumentsBrowser + + + Copy + 复制 + + + + Export + 导出 + + + + CenterWidget + + + Python entry script path + Python 入口脚本路径 + + + + + Browse + 浏览 + + + + Python script: + 待打包脚本: + + + + App Name: + 项目名称: + + + + Bundled app name + 打包的应用程序名称 + + + + One Folder/ One File: + 单目录/单文件: + + + + One Folder + 打包至单个目录 + + + + One File + 打包至单个文件 + + + + + Add Data Files + 添加数据文件 + + + + + Add Binary Files + 添加二进制文件 + + + + Hidden Import + 隐式导入 + + + + Clean + 清理 + + + + Bundle! + 打包! + + + + Bundling into one folder. + 将打包至单个目录中。 + + + + Bundling into one file. + 将打包至单个文件中。 + + + + Add data files updated. + 添加数据文件已更新。 + + + + Add binary files updated. + 添加二进制文件已更新。 + + + + Hidden import updated. + 隐式导入已更新。 + + + + Clean cache and remove temporary files before building. + 构建前将清除缓存与临时目录。 + + + + Will not delete cache and temporary files. + 不会删除缓存与临时文件。 + + + + Opened script path: + 打开脚本路径: + + + + The app name has been set to: + 已将项目名设置为: + + + + + Error + 错误 + + + + The selection is not a valid Python script file, please reselect it! + 选择的不是有效的Python脚本文件,请重新选择! + + + + Warning + 警告 + + + + Pyinstaller doesn't seem to be installed in this Python environment, still continue? + 在该 Python 环境中似乎没有安装 Pyinstaller,是否仍要继续? + + + + The selection is not a valid Python interpreter, please reselect it! + 选择的不是有效的Python解释器,请重新选择! + + + + IconFileDlg + + + Icon Files (*.ico *.icns) + 图标文件 (*.ico *.icns) + + + + All Files (*) + 所有文件 (*) + + + + App Icon + 图标 + + + + Icon File + 图标文件 + + + + Open + 打开 + + + + Cancel + 取消 + + + + InterpreterFileDlg + + + Python Interpreter + Python 解释器 + + + + Executable File + 可执行文件 + + + + Python Interpreter (python.exe) + Python 解释器 (python.exe) + + + + Executable Files (*.exe) + 可执行文件 (*.exe) + + + + + All Files (*) + 所有文件 (*) + + + + Python Interpreter (python3*) + Python 解释器 (python3*) + + + + MainApp + + + Ready. + 就绪。 + + + + MainWindow + + + &File + 文件(&F) + + + + Import Config From JSON File + 从 JSON 文件中导入配置 + + + + Export Config To JSON File + 导出配置至 JSON 文件 + + + + &Settings + 设置(&S) + + + + &Exit + 退出(&E) + + + + &Help + 帮助(&H) + + + + PyInstaller Documentation + PyInstaller 文档 + + + + PyInstaller Options Details + PyInstaller 选项详情 + + + + Report Bugs + 报告 Bug + + + + &About + 关于(&A) + + + + About This Program + 关于此程序 + + + + About &Qt + 关于 &Qt + + + + MultiItemEditWindow + + + &New + 新建(&N) + + + + &Delete + 删除(&D) + + + + &OK + 确认(&O) + + + + &Cancel + 取消(&C) + + + + MultiPkgEditWindow + + + &Browse packages + 浏览包(&B) + + + + PkgBrowserDlg + + + Installed Packages + 已安装的包 + + + + Name + 这里专指软件包的名称 + 包名 + + + + Version + 版本 + + + + PyinstallerOptionTable + + + Option + 选项 + + + + Description + 描述 + + + + ScriptFileDlg + + + Python Script (*.py *.pyw) + Python 脚本文件 (*.py *.pyw) + + + + All Files (*) + 所有文件 (*) + + + + Python Entry File + Python入口文件 + + + + Python File + Python 文件 + + + + Open + 打开 + + + + Cancel + 取消 + + + + SubProcessDlg + + + Done! + 此处专指PyInstaller运行完成。 + 打包完成! + + + + + Open Dist + 打开输出位置 + + + + Execution ends, but an error occurs and the exit code is + 运行结束,但有错误发生,退出码为 + + + + + + Cancel + 取消 + + + + PyInstaller Error! + PyInstaller 错误! + + + + PyInstaller subprocess output: + PyInstaller 子进程输出: + + + + Please check if you have installed the correct version of PyInstaller or not. + 请检查是否已经安装正确版本的 PyInstaller。 + + + + + Close + 关闭 + + + + WinMacCenterWidget + + + App icon: + 应用图标: + + + + Path to icon file + 图标路径 + + + + Browse + 浏览 + + + + Open a console window for standard I/O + 为标准I/O启用终端 + + + + Terminal will be enabled. + 将为打包程序的 stdio 启用终端。 + + + + Terminal will not be enabled. + 不会为打包程序的 stdio 启用终端。 + + + + Opened icon path: + 打开的图标路径: + + + + Error + 错误 + + + + The selection is not a valid icon file, please re-select it! + 选择的不是有效的图标文件,请重新选择! + + + diff --git a/src/py2exe_gui/Widgets/add_data_widget.py b/src/py2exe_gui/Widgets/add_data_widget.py index 4d9f83c..9d1c7d9 100644 --- a/src/py2exe_gui/Widgets/add_data_widget.py +++ b/src/py2exe_gui/Widgets/add_data_widget.py @@ -6,14 +6,12 @@ __all__ = ["AddDataWindow"] -import sys from pathlib import Path from typing import Optional from PySide6.QtCore import QItemSelectionModel, Qt, Signal, Slot from PySide6.QtGui import QIcon, QPixmap from PySide6.QtWidgets import ( - QApplication, QFileDialog, QHBoxLayout, QHeaderView, @@ -24,8 +22,10 @@ from PySide6.QtWidgets import ( QWidget, ) +from ..Utilities import QObjTr -class AddDataWindow(QWidget): + +class AddDataWindow(QObjTr, QWidget): """用于提供 PyInstaller --add-data 和 --add-binary 功能的窗口""" # 类型别名 @@ -68,7 +68,7 @@ class AddDataWindow(QWidget): def _setup_ui(self) -> None: """处理 UI 内容""" - self.setWindowTitle("添加文件") + self.setWindowTitle(AddDataWindow.tr("Add Files")) self.setMinimumWidth(550) self.setWindowIcon(QIcon(QPixmap(":/Icons/Py2exe-GUI_icon_72px"))) @@ -85,13 +85,13 @@ class AddDataWindow(QWidget): 1, QHeaderView.ResizeMode.ResizeToContents ) - self.new_btn.setText("新建(&N)") - self.browse_btn.setText("浏览文件(&B)") - self.browse_dir_btn.setText("浏览目录(&F)") - self.delete_btn.setText("删除(&D)") + self.new_btn.setText(AddDataWindow.tr("&New")) + self.browse_btn.setText(AddDataWindow.tr("&Browse File")) + self.browse_dir_btn.setText(AddDataWindow.tr("Browse &Folder")) + self.delete_btn.setText(AddDataWindow.tr("&Delete")) - self.ok_btn.setText("确定") - self.cancel_btn.setText("取消") + self.ok_btn.setText(AddDataWindow.tr("&OK")) + self.cancel_btn.setText(AddDataWindow.tr("&Cancel")) # noinspection DuplicatedCode def _setup_layout(self) -> None: @@ -119,6 +119,7 @@ class AddDataWindow(QWidget): self.setLayout(main_layout) + # noinspection DuplicatedCode def _connect_slots(self) -> None: """构建各槽函数、连接信号""" @@ -212,7 +213,6 @@ class AddDataWindow(QWidget): self.data_selected.emit(self._submit()) self.close() - # noinspection DuplicatedCode self.new_btn.clicked.connect(handle_new_btn) self.delete_btn.clicked.connect(handle_delete_btn) self.browse_btn.clicked.connect(handel_browse_btn) @@ -261,10 +261,3 @@ class AddDataWindow(QWidget): ) self.item_table.setItem(row, 0, source_item) self.item_table.setItem(row, 1, QTableWidgetItem(data_item[1])) - - -if __name__ == "__main__": - app = QApplication(sys.argv) - window = AddDataWindow() - window.show() - sys.exit(app.exec()) diff --git a/src/py2exe_gui/Widgets/arguments_browser.py b/src/py2exe_gui/Widgets/arguments_browser.py index 12ac2f8..c55ddba 100644 --- a/src/py2exe_gui/Widgets/arguments_browser.py +++ b/src/py2exe_gui/Widgets/arguments_browser.py @@ -4,7 +4,10 @@ """此模块主要包含用于在界面上预览显示 PyInstaller 命令选项的 `ArgumentsBrowser` 类 """ -__all__ = ["get_line_continuation", "ArgumentsBrowser"] +__all__ = [ + "get_line_continuation", + "ArgumentsBrowser", +] from typing import Optional @@ -12,6 +15,7 @@ from PySide6.QtGui import QContextMenuEvent from PySide6.QtWidgets import QMenu, QTextBrowser, QWidget from ..Constants import RUNTIME_INFO, Platform +from ..Utilities import QObjTr # 一组适合浅色背景的颜色 colors = ["#FD6D5A", "#FEB40B", "#6DC354", "#994487", "#518CD8", "#443295"] @@ -46,7 +50,7 @@ def wrap_font_tag(raw_text: str, color: str, **kwargs): return f"{raw_text}" -class ArgumentsBrowser(QTextBrowser): +class ArgumentsBrowser(QObjTr, QTextBrowser): """针对命令行参数列表特别优化的文本浏览器""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -58,9 +62,9 @@ class ArgumentsBrowser(QTextBrowser): # 右键菜单 self.context_menu = QMenu(self) - copy_action = self.context_menu.addAction("复制") + copy_action = self.context_menu.addAction(ArgumentsBrowser.tr("Copy")) copy_action.triggered.connect(self._handle_copy_action) - export_action = self.context_menu.addAction("导出") + export_action = self.context_menu.addAction(ArgumentsBrowser.tr("Export")) export_action.triggered.connect(self._handle_export_action) def contextMenuEvent(self, event: QContextMenuEvent) -> None: diff --git a/src/py2exe_gui/Widgets/center_widget.py b/src/py2exe_gui/Widgets/center_widget.py index 0f81681..057dda4 100644 --- a/src/py2exe_gui/Widgets/center_widget.py +++ b/src/py2exe_gui/Widgets/center_widget.py @@ -31,7 +31,7 @@ from PySide6.QtWidgets import ( from ..Constants import PyInstOpt from ..Core import InterpreterValidator -from ..Utilities import ALL_PY_ENVs, PyEnv +from ..Utilities import ALL_PY_ENVs, PyEnv, QObjTr from .add_data_widget import AddDataWindow from .arguments_browser import ArgumentsBrowser from .dialog_widgets import ( @@ -45,7 +45,7 @@ from .pyenv_combobox import PyEnvComboBox from .pyinstaller_option_widget import PyinstallerOptionTable -class CenterWidget(QWidget): +class CenterWidget(QObjTr, QWidget): """主界面的中央控件 此类为可以实例化的基类,适用于所有平台。 @@ -54,7 +54,6 @@ class CenterWidget(QWidget): # 自定义信号 option_selected = QtCore.Signal(tuple) # 用户通过界面控件选择选项后发射此信号 - # option_selected 实际类型为 tuple[PyinstallerArgs, str] def __init__(self, parent: QMainWindow) -> None: @@ -122,38 +121,40 @@ class CenterWidget(QWidget): def _setup_ui(self) -> None: """设置各种控件的属性""" - self.script_path_label.setText("待打包脚本:") + self.script_path_label.setText(CenterWidget.tr("Python script:")) self.script_path_le.setReadOnly(True) - self.script_path_le.setPlaceholderText("Python入口文件路径") - self.script_browse_btn.setText("浏览") + self.script_path_le.setPlaceholderText( + CenterWidget.tr("Python entry script path") + ) + self.script_browse_btn.setText(CenterWidget.tr("Browse")) self.script_browse_btn.setFixedWidth(80) - self.pyenv_browse_btn.setText("浏览") + self.pyenv_browse_btn.setText(CenterWidget.tr("Browse")) self.pyenv_browse_btn.setFixedWidth(80) - self.project_name_label.setText("项目名称:") - self.project_name_le.setPlaceholderText("打包的应用程序名称") + self.project_name_label.setText(CenterWidget.tr("App Name:")) + self.project_name_le.setPlaceholderText(CenterWidget.tr("Bundled app name")) - self.fd_label.setText("单文件/单目录:") - self.one_dir_btn.setText("打包至单个目录") + self.fd_label.setText(CenterWidget.tr("One Folder/ One File:")) + self.one_dir_btn.setText(CenterWidget.tr("One Folder")) self.one_dir_btn.setChecked(True) # 默认值 - self.one_file_btn.setText("打包至单个文件") + self.one_file_btn.setText(CenterWidget.tr("One File")) self.fd_group.addButton(self.one_dir_btn, 0) self.fd_group.addButton(self.one_file_btn, 1) - self.add_data_btn.setText("添加数据文件") - self.add_binary_btn.setText("添加二进制文件") - self.add_data_dlg.setWindowTitle("添加数据文件") - self.add_binary_dlg.setWindowTitle("添加二进制文件") + self.add_data_btn.setText(CenterWidget.tr("Add Data Files")) + self.add_binary_btn.setText(CenterWidget.tr("Add Binary Files")) + self.add_data_dlg.setWindowTitle(CenterWidget.tr("Add Data Files")) + self.add_binary_dlg.setWindowTitle(CenterWidget.tr("Add Binary Files")) - self.hidden_import_btn.setText("隐式导入") - self.hidden_import_dlg.setWindowTitle("Hidden import") + self.hidden_import_btn.setText(CenterWidget.tr("Hidden Import")) + self.hidden_import_dlg.setWindowTitle("Hidden Import") - self.clean_checkbox.setText("清理") + self.clean_checkbox.setText(CenterWidget.tr("Clean")) self.clean_checkbox.setChecked(False) self.pyinstaller_args_browser.setMaximumHeight(80) - self.run_packaging_btn.setText("打包!") + self.run_packaging_btn.setText(CenterWidget.tr("Bundle!")) self.run_packaging_btn.setEnabled(False) # 将 PyInstaller 选项详情设置成各控件的 ToolTip @@ -183,16 +184,13 @@ class CenterWidget(QWidget): self.option_selected.emit((PyInstOpt.script_path, file_path)) - @QtCore.Slot(int) - def handle_pyenv_change(new_index: int) -> None: - """处理用户通过选择不同的 Python 解释器时的响应 - - :param new_index: 选择的 Python 解释器在 `pyenv_combobox` 中的索引 - """ + @QtCore.Slot() + def handle_pyenv_change() -> None: + """处理用户通过选择不同的 Python 解释器时的响应""" current_pyenv = self.pyenv_combobox.get_current_pyenv() - # TODO 设置为懒加载,用户打开“已安装的包”对话框时才运行 + # 首次调用 current_pyenv.installed_packages 为重大性能热点,优化时应首先考虑 self.pkg_browser_dlg.load_pkg_list(current_pyenv.installed_packages) @QtCore.Slot() @@ -211,10 +209,10 @@ class CenterWidget(QWidget): if btn_id == 0: self.option_selected.emit((PyInstOpt.FD, "--onedir")) - self.parent_widget.statusBar().showMessage("将打包至单个目录中") + self.show_status_msg(CenterWidget.tr("Bundling into one folder.")) elif btn_id == 1: self.option_selected.emit((PyInstOpt.FD, "--onefile")) - self.parent_widget.statusBar().showMessage("将打包至单个文件中") + self.show_status_msg(CenterWidget.tr("Bundling into one file.")) @QtCore.Slot() def handle_add_data_btn_clicked() -> None: @@ -228,7 +226,7 @@ class CenterWidget(QWidget): """用户完成了添加数据操作的槽函数""" self.data_item_list = data_item_list - self.parent_widget.statusBar().showMessage("添加数据文件已更新") + self.show_status_msg(CenterWidget.tr("Add data files updated.")) self.option_selected.emit((PyInstOpt.add_data, data_item_list)) @QtCore.Slot() @@ -243,7 +241,7 @@ class CenterWidget(QWidget): """用户完成了添加二进制文件操作的槽函数""" self.binary_item_list = binary_item_list - self.parent_widget.statusBar().showMessage("添加二进制文件已更新") + self.show_status_msg(CenterWidget.tr("Add binary files updated.")) self.option_selected.emit((PyInstOpt.add_binary, binary_item_list)) @QtCore.Slot() @@ -260,7 +258,7 @@ class CenterWidget(QWidget): """ self.hidden_import_list = hidden_import_list - self.parent_widget.statusBar().showMessage("隐式导入已更新") + self.show_status_msg(CenterWidget.tr("Hidden import updated.")) self.option_selected.emit((PyInstOpt.hidden_import, hidden_import_list)) @QtCore.Slot(bool) @@ -272,10 +270,16 @@ class CenterWidget(QWidget): if selected: self.option_selected.emit((PyInstOpt.clean, "--clean")) - self.parent_widget.statusBar().showMessage("构建前将清理缓存与临时文件") + self.show_status_msg( + CenterWidget.tr( + "Clean cache and remove temporary files before building." + ) + ) else: self.option_selected.emit((PyInstOpt.clean, "")) - self.parent_widget.statusBar().showMessage("不会删除缓存与临时文件") + self.show_status_msg( + CenterWidget.tr("Will not delete cache and temporary files.") + ) # 连接信号与槽 # 入口脚本文件 @@ -285,7 +289,7 @@ class CenterWidget(QWidget): # 添加与选择 Python 解释器 self.pyenv_browse_btn.clicked.connect(self.itp_dlg.open) self.itp_dlg.fileSelected.connect(self._handle_itp_file_selected) - handle_pyenv_change(0) # 显式调用一次,为默认项设置相关内容 + handle_pyenv_change() # 显式调用一次,为默认项设置相关内容 self.pyenv_combobox.currentIndexChanged.connect(handle_pyenv_change) # 项目名称 @@ -366,15 +370,18 @@ class CenterWidget(QWidget): if option_key == PyInstOpt.script_path: script_path = Path(option_value) self.script_path_le.setText(script_path.name) - self.parent_widget.statusBar().showMessage( - f"打开脚本路径:{str(script_path.absolute())}" + self.show_status_msg( + CenterWidget.tr("Opened script path: ") + + f"{str(script_path.absolute())}" ) self.add_data_dlg.set_work_dir(script_path.parent) self.add_binary_dlg.set_work_dir(script_path.parent) elif option_key == PyInstOpt.out_name: self.project_name_le.setText(option_value) - self.parent_widget.statusBar().showMessage(f"已将项目名设置为:{option_value}") + self.show_status_msg( + CenterWidget.tr("The app name has been set to:") + f"{option_value}" + ) @QtCore.Slot(PyInstOpt) def handle_option_error(self, option: PyInstOpt) -> None: @@ -389,8 +396,11 @@ class CenterWidget(QWidget): # 警告对话框 result = QMessageBox.critical( self, - "错误", - "选择的不是有效的Python脚本文件,请重新选择!", + CenterWidget.tr("Error"), + CenterWidget.tr( + "The selection is not a valid Python script file, " + "please reselect it!" + ), QMessageBox.StandardButton.Cancel, QMessageBox.StandardButton.Ok, ) @@ -437,8 +447,11 @@ class CenterWidget(QWidget): # TODO 自实现 MessageBox,包含"仍要使用"、"取消"、"尝试安装PyInstaller"三个按钮 result = QMessageBox.warning( self, - "警告", - "在该 Python 环境中似乎没有安装 Pyinstaller," "是否仍要继续?", + CenterWidget.tr("Warning"), + CenterWidget.tr( + "Pyinstaller doesn't seem to be installed in this Python " + "environment, still continue?" + ), QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Cancel, ) @@ -456,14 +469,25 @@ class CenterWidget(QWidget): self.itp_dlg.close() result = QMessageBox.critical( self, - "错误", - "选择的不是有效的Python解释器,请重新选择!", + CenterWidget.tr("Error"), + CenterWidget.tr( + "The selection is not a valid Python interpreter, " + "please reselect it!" + ), QMessageBox.StandardButton.Cancel, QMessageBox.StandardButton.Ok, ) if result == QMessageBox.StandardButton.Ok: self.itp_dlg.exec() + def show_status_msg(self, msg: str) -> None: + """向父控件(QMainWindow)的状态栏显示信息 + + :param msg: 要在状态栏显示的信息 + """ + + self.parent_widget.statusBar().showMessage(msg) + class WinMacCenterWidget(CenterWidget): """包含 Windows 与 MacOS 特有功能的主界面中央控件 @@ -492,13 +516,15 @@ class WinMacCenterWidget(CenterWidget): super()._setup_ui() - self.icon_path_label.setText("应用图标:") + self.icon_path_label.setText(WinMacCenterWidget.tr("App icon:")) self.icon_path_le.setReadOnly(True) - self.icon_path_le.setPlaceholderText("图标文件路径") - self.icon_browse_btn.setText("浏览") + self.icon_path_le.setPlaceholderText(WinMacCenterWidget.tr("Path to icon file")) + self.icon_browse_btn.setText(WinMacCenterWidget.tr("Browse")) self.icon_browse_btn.setFixedWidth(80) - self.console_checkbox.setText("为标准I/O启用终端") + self.console_checkbox.setText( + WinMacCenterWidget.tr("Open a console window for standard I/O") + ) self.console_checkbox.setChecked(True) # 默认值 # 将 PyInstaller 选项详情设置成各控件的 ToolTip @@ -520,8 +546,8 @@ class WinMacCenterWidget(CenterWidget): @QtCore.Slot(str) def handle_icon_file_selected(file_path: str) -> None: - """ - 图标文件完成选择的槽函数 \n + """图标文件完成选择的槽函数 + :param file_path: 图标路径 """ @@ -536,10 +562,12 @@ class WinMacCenterWidget(CenterWidget): if console: self.option_selected.emit((PyInstOpt.console, "--console")) - self.parent_widget.statusBar().showMessage("将为打包程序的 stdio 启用终端") + self.show_status_msg(WinMacCenterWidget.tr("Terminal will be enabled.")) else: self.option_selected.emit((PyInstOpt.console, "--windowed")) - self.parent_widget.statusBar().showMessage("不会为打包程序的 stdio 启用终端") + self.show_status_msg( + WinMacCenterWidget.tr("Terminal will not be enabled.") + ) # 图标文件 self.icon_browse_btn.clicked.connect(self.icon_file_dlg.open) @@ -582,9 +610,11 @@ class WinMacCenterWidget(CenterWidget): elif option_key == PyInstOpt.icon_path: icon_path = Path(option_value) self.icon_path_le.setText(icon_path.name) - self.parent_widget.statusBar().showMessage( - f"打开图标路径:{str(icon_path.absolute())}" + msg = ( + WinMacCenterWidget.tr("Opened icon path: ") + + f"{str(icon_path.absolute())}" ) + self.show_status_msg(msg) @QtCore.Slot(PyInstOpt) def handle_option_error(self, option: PyInstOpt) -> None: @@ -599,8 +629,10 @@ class WinMacCenterWidget(CenterWidget): self.icon_file_dlg.close() result = QMessageBox.critical( self.parent_widget, - "错误", - "选择的不是有效的图标文件,请重新选择!", + WinMacCenterWidget.tr("Error"), + WinMacCenterWidget.tr( + "The selection is not a valid icon file, please re-select it!" + ), QMessageBox.StandardButton.Cancel, QMessageBox.StandardButton.Ok, ) diff --git a/src/py2exe_gui/Widgets/dialog_widgets.py b/src/py2exe_gui/Widgets/dialog_widgets.py index 29329c1..27bd24a 100644 --- a/src/py2exe_gui/Widgets/dialog_widgets.py +++ b/src/py2exe_gui/Widgets/dialog_widgets.py @@ -29,10 +29,10 @@ from PySide6.QtWidgets import ( ) from ..Constants import RUNTIME_INFO, Platform -from ..Utilities import QtFileOpen +from ..Utilities import QObjTr, QtFileOpen -class ScriptFileDlg(QFileDialog): +class ScriptFileDlg(QObjTr, QFileDialog): """用于获取入口脚本文件的对话框""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -44,15 +44,24 @@ class ScriptFileDlg(QFileDialog): self.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) self.setViewMode(QFileDialog.ViewMode.Detail) - self.setNameFilters(("Python脚本文件 (*.py *.pyw)", "所有文件 (*)")) + self.setNameFilters( + ( + ScriptFileDlg.tr("Python Script (*.py *.pyw)"), + ScriptFileDlg.tr("All Files (*)"), + ) + ) self.setFileMode(QFileDialog.FileMode.ExistingFiles) - self.setLabelText(QFileDialog.DialogLabel.FileName, "Python入口文件") - self.setLabelText(QFileDialog.DialogLabel.FileType, "Python文件") - self.setLabelText(QFileDialog.DialogLabel.Accept, "打开") - self.setLabelText(QFileDialog.DialogLabel.Reject, "取消") + self.setLabelText( + QFileDialog.DialogLabel.FileName, ScriptFileDlg.tr("Python Entry File") + ) + self.setLabelText( + QFileDialog.DialogLabel.FileType, ScriptFileDlg.tr("Python File") + ) + self.setLabelText(QFileDialog.DialogLabel.Accept, ScriptFileDlg.tr("Open")) + self.setLabelText(QFileDialog.DialogLabel.Reject, ScriptFileDlg.tr("Cancel")) -class IconFileDlg(QFileDialog): +class IconFileDlg(QObjTr, QFileDialog): """用于获取应用图标文件的对话框""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -64,15 +73,20 @@ class IconFileDlg(QFileDialog): self.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) self.setViewMode(QFileDialog.ViewMode.Detail) - self.setNameFilters(("图标文件 (*.ico *.icns)", "所有文件 (*)")) + self.setNameFilters( + ( + IconFileDlg.tr("Icon Files (*.ico *.icns)"), + IconFileDlg.tr("All Files (*)"), + ) + ) self.setFileMode(QFileDialog.FileMode.ExistingFile) - self.setLabelText(QFileDialog.DialogLabel.FileName, "图标") - self.setLabelText(QFileDialog.DialogLabel.FileType, "图标文件") - self.setLabelText(QFileDialog.DialogLabel.Accept, "打开") - self.setLabelText(QFileDialog.DialogLabel.Reject, "取消") + self.setLabelText(QFileDialog.DialogLabel.FileName, IconFileDlg.tr("App Icon")) + self.setLabelText(QFileDialog.DialogLabel.FileType, IconFileDlg.tr("Icon File")) + self.setLabelText(QFileDialog.DialogLabel.Accept, IconFileDlg.tr("Open")) + self.setLabelText(QFileDialog.DialogLabel.Reject, IconFileDlg.tr("Cancel")) -class InterpreterFileDlg(QFileDialog): +class InterpreterFileDlg(QObjTr, QFileDialog): """用于获取 Python 解释器可执行文件的对话框""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -85,17 +99,32 @@ class InterpreterFileDlg(QFileDialog): self.setAcceptMode(QFileDialog.AcceptMode.AcceptOpen) self.setViewMode(QFileDialog.ViewMode.Detail) self.setFileMode(QFileDialog.FileMode.ExistingFile) - self.setLabelText(QFileDialog.DialogLabel.FileName, "Python解释器") - self.setLabelText(QFileDialog.DialogLabel.FileType, "可执行文件") + self.setLabelText( + QFileDialog.DialogLabel.FileName, + InterpreterFileDlg.tr("Python Interpreter"), + ) + self.setLabelText( + QFileDialog.DialogLabel.FileType, InterpreterFileDlg.tr("Executable File") + ) - # 进行一定的文件过滤 - # 目前已知的可行方法只有 Windows 下按文件后缀,Linux 下似乎不可行 - # https://stackoverflow.com/questions/50213049/ if RUNTIME_INFO.platform == Platform.windows: - self.setNameFilters(("可执行文件 (*.exe)", "所有文件 (*)")) + self.setNameFilters( + ( + InterpreterFileDlg.tr("Python Interpreter (python.exe)"), + InterpreterFileDlg.tr("Executable Files (*.exe)"), + InterpreterFileDlg.tr("All Files (*)"), + ) + ) + else: + self.setNameFilters( + ( + InterpreterFileDlg.tr("Python Interpreter (python3*)"), + InterpreterFileDlg.tr("All Files (*)"), + ) + ) -class AboutDlg(QMessageBox): +class AboutDlg(QObjTr, QMessageBox): """用于显示关于信息的对话框""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -106,7 +135,7 @@ class AboutDlg(QMessageBox): super().__init__(parent) self._about_text: str = "" - self.setWindowTitle("关于") + self.setWindowTitle(AboutDlg.tr("About")) self.setStandardButtons(QMessageBox.StandardButton.Ok) self.setTextFormat(Qt.TextFormat.MarkdownText) self.setText(self.about_text) @@ -124,13 +153,17 @@ class AboutDlg(QMessageBox): with QtFileOpen(":/Texts/About_Text", encoding="utf-8") as about_file: self._about_text = about_file.read() except OSError as e: - warnings.warn(f"无法打开关于文档,错误信息:{e}", RuntimeWarning, stacklevel=1) - self._about_text = "无法打开关于文档,请尝试重新获取本程序。" + warnings.warn( + f"Cannot open About document: {e}", RuntimeWarning, stacklevel=1 + ) + self._about_text = AboutDlg.tr( + "Can't open the About document, try to reinstall this program." + ) return self._about_text -class PkgBrowserDlg(QDialog): +class PkgBrowserDlg(QObjTr, QDialog): """浏览已安装的所有包的对话框""" def __init__(self, parent: Optional[QWidget] = None) -> None: @@ -148,11 +181,13 @@ class PkgBrowserDlg(QDialog): def _setup_ui(self) -> None: """处理 UI""" - self.setWindowTitle("已安装的包") + self.setWindowTitle(PkgBrowserDlg.tr("Installed Packages")) self.setWindowIcon(QIcon(QPixmap(":/Icons/Python_128px"))) self.pkg_table.setColumnCount(2) - self.pkg_table.setHorizontalHeaderLabels(["包名", "版本"]) + self.pkg_table.setHorizontalHeaderLabels( + [PkgBrowserDlg.tr("Name"), PkgBrowserDlg.tr("Version")] + ) self.pkg_table.horizontalHeader().setSectionResizeMode( 0, QHeaderView.ResizeMode.Stretch ) diff --git a/src/py2exe_gui/Widgets/main_window.py b/src/py2exe_gui/Widgets/main_window.py index c06e9c1..34e543b 100644 --- a/src/py2exe_gui/Widgets/main_window.py +++ b/src/py2exe_gui/Widgets/main_window.py @@ -13,9 +13,9 @@ from PySide6.QtGui import QDesktopServices, QIcon, QPixmap from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QMenuBar, QStatusBar from ..Constants import RUNTIME_INFO, APP_URLs, AppConstant, Platform +from ..Utilities.qobject_tr import QObjTr from .center_widget import CenterWidget, WinMacCenterWidget from .dialog_widgets import AboutDlg -from .pyinstaller_option_widget import PyinstallerOptionTable def open_url(url: str) -> None: @@ -27,7 +27,7 @@ def open_url(url: str) -> None: QDesktopServices.openUrl(QUrl(url)) -class MainWindow(QMainWindow): +class MainWindow(QObjTr, QMainWindow): """ 主界面主窗口 """ @@ -64,29 +64,32 @@ class MainWindow(QMainWindow): def _setup_menu_bar(self) -> None: """配置主窗口菜单栏""" - file_menu = self.menu_bar.addMenu("文件(&F)") - file_menu.addAction("打开打包任务") # 暂时只为占位 - file_menu.addAction("保存当前打包任务") # 暂时只为占位 + file_menu = self.menu_bar.addMenu(MainWindow.tr("&File")) + file_menu.addAction(MainWindow.tr("Import Config From JSON File")) # 暂时只为占位 + file_menu.addAction(MainWindow.tr("Export Config To JSON File")) # 暂时只为占位 file_menu.addSeparator() - file_menu.addAction("设置") # 暂时只为占位 + file_menu.addAction(MainWindow.tr("&Settings")) # 暂时只为占位 file_menu.addSeparator() - file_menu.addAction("退出(&X)", self.close) + file_menu.addAction(MainWindow.tr("&Exit"), self.close) - help_menu = self.menu_bar.addMenu("帮助(&H)") + help_menu = self.menu_bar.addMenu(MainWindow.tr("&Help")) help_menu.addAction( - "PyInstaller官方文档", + MainWindow.tr("PyInstaller Documentation"), lambda: open_url(APP_URLs["Pyinstaller_doc"]), ) help_menu.addAction( - "PyInstaller选项详情", self.center_widget.pyinstaller_option_table.show + MainWindow.tr("PyInstaller Options Details"), + self.center_widget.pyinstaller_option_table.show, ) help_menu.addSeparator() - help_menu.addAction("报告Bug", lambda: open_url(APP_URLs["BugTracker"])) + help_menu.addAction( + MainWindow.tr("Report Bugs"), lambda: open_url(APP_URLs["BugTracker"]) + ) - about_menu = self.menu_bar.addMenu("关于(&A)") - about_menu.addAction("关于本程序", AboutDlg(self).exec) - about_menu.addAction("关于 &Qt", QApplication.aboutQt) + about_menu = self.menu_bar.addMenu(MainWindow.tr("&About")) + about_menu.addAction(MainWindow.tr("About This Program"), AboutDlg(self).exec) + about_menu.addAction(MainWindow.tr("About &Qt"), QApplication.aboutQt) def _setup_status_bar(self) -> None: """配置主窗口状态栏""" diff --git a/src/py2exe_gui/Widgets/multi_item_edit_widget.py b/src/py2exe_gui/Widgets/multi_item_edit_widget.py index e94e090..3e7b33c 100644 --- a/src/py2exe_gui/Widgets/multi_item_edit_widget.py +++ b/src/py2exe_gui/Widgets/multi_item_edit_widget.py @@ -8,7 +8,10 @@ `MultiPkgEditWindow` 继承自 `MultiItemEditWindow`,多了一个浏览当前 Python 环境中已安装 Python 包的功能 """ -__all__ = ["MultiItemEditWindow", "MultiPkgEditWindow"] +__all__ = [ + "MultiItemEditWindow", + "MultiPkgEditWindow", +] from typing import Optional @@ -23,10 +26,11 @@ from PySide6.QtWidgets import ( QWidget, ) +from ..Utilities import QObjTr from .dialog_widgets import PkgBrowserDlg -class MultiItemEditWindow(QWidget): +class MultiItemEditWindow(QObjTr, QWidget): """用于添加多个条目的窗口控件,实现如 --hidden-import、--collect-submodules 等功能""" items_selected = Signal(list) # 用户在添加条目窗口完成所有编辑后,提交的信号.完整数据类型为 list[str] @@ -65,11 +69,11 @@ class MultiItemEditWindow(QWidget): self.setWindowIcon(QIcon(QPixmap(":/Icons/Py2exe-GUI_icon_72px"))) - self.new_btn.setText("新建(&N)") - self.delete_btn.setText("删除(&D)") + self.new_btn.setText(MultiItemEditWindow.tr("&New")) + self.delete_btn.setText(MultiItemEditWindow.tr("&Delete")) - self.ok_btn.setText("确定") - self.cancel_btn.setText("取消") + self.ok_btn.setText(MultiItemEditWindow.tr("&OK")) + self.cancel_btn.setText(MultiItemEditWindow.tr("&Cancel")) # noinspection DuplicatedCode def _setup_layout(self) -> None: @@ -185,7 +189,7 @@ class MultiPkgEditWindow(MultiItemEditWindow): """处理 UI""" super()._setup_ui() - self.browse_pkg_button.setText("浏览包(&B)") + self.browse_pkg_button.setText(MultiPkgEditWindow.tr("&Browse packages")) def _setup_layout(self) -> None: """构建与设置布局管理器""" diff --git a/src/py2exe_gui/Widgets/pyinstaller_option_widget.py b/src/py2exe_gui/Widgets/pyinstaller_option_widget.py index e012cca..f7c2c08 100644 --- a/src/py2exe_gui/Widgets/pyinstaller_option_widget.py +++ b/src/py2exe_gui/Widgets/pyinstaller_option_widget.py @@ -7,7 +7,10 @@ `PyinstallerOptionTable` 类是用于显示 PyInstaller 命令行选项的表格控件窗口,界面有待进一步优化 """ -__all__ = ["load_pyinst_options", "PyinstallerOptionTable"] +__all__ = [ + "load_pyinst_options", + "PyinstallerOptionTable", +] import warnings @@ -16,7 +19,7 @@ from PySide6.QtGui import QPixmap from PySide6.QtWidgets import QHeaderView, QTableWidget, QTableWidgetItem from ..Constants import RUNTIME_INFO -from ..Utilities import QtFileOpen +from ..Utilities import QObjTr, QtFileOpen def load_pyinst_options() -> dict[str, str]: @@ -33,7 +36,7 @@ def load_pyinst_options() -> dict[str, str]: option_file_text = option_file.read() except OSError as e: warnings.warn( - f"PyInstaller Options 加载失败,错误信息:{e}", RuntimeWarning, stacklevel=1 + f"Failed to load PyInstaller Options: {e}", RuntimeWarning, stacklevel=1 ) return dict() @@ -57,7 +60,7 @@ def load_pyinst_options() -> dict[str, str]: return option_dict -class PyinstallerOptionTable(QTableWidget): +class PyinstallerOptionTable(QObjTr, QTableWidget): """用于显示 PyInstaller 命令行选项的表格控件""" def __init__(self) -> None: @@ -68,7 +71,12 @@ class PyinstallerOptionTable(QTableWidget): self.setMinimumSize(700, 430) self.setWindowIcon(QPixmap(":/Icons/PyInstaller")) self.setColumnCount(2) - self.setHorizontalHeaderLabels(["选项", "描述"]) + self.setHorizontalHeaderLabels( + [ + PyinstallerOptionTable.tr("Option"), + PyinstallerOptionTable.tr("Description"), + ] + ) self.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) # 存储选项信息的字典 diff --git a/src/py2exe_gui/Widgets/subprocess_widget.py b/src/py2exe_gui/Widgets/subprocess_widget.py index b74006e..56ef438 100644 --- a/src/py2exe_gui/Widgets/subprocess_widget.py +++ b/src/py2exe_gui/Widgets/subprocess_widget.py @@ -18,9 +18,10 @@ from PySide6.QtWidgets import ( ) from ..Core.subprocess_tool import SubProcessTool +from ..Utilities import QObjTr -class SubProcessDlg(QDialog): +class SubProcessDlg(QObjTr, QDialog): """用于显示子进程信息的对话框""" def __init__(self, parent: QWidget) -> None: @@ -64,7 +65,7 @@ class SubProcessDlg(QDialog): if output_type == SubProcessTool.OutputType.STATE: self.info_label.setText(output_text) if output_text == "The process is running...": - self.multifunction_btn.setText("取消") + self.multifunction_btn.setText(SubProcessDlg.tr("Cancel")) elif ( output_type == SubProcessTool.OutputType.STDOUT @@ -74,20 +75,32 @@ class SubProcessDlg(QDialog): elif output_type == SubProcessTool.OutputType.FINISHED: if output_text == "0": - self.info_label.setText("打包完成!") - self.multifunction_btn.setText("打开输出位置") + self.info_label.setText(SubProcessDlg.tr("Done!")) + self.multifunction_btn.setText(SubProcessDlg.tr("Open Dist")) else: - self.info_label.setText(f"运行结束,但有错误发生,退出码为 {output_text}") - self.multifunction_btn.setText("取消") + self.info_label.setText( + SubProcessDlg.tr( + "Execution ends, but an error occurs and the exit code is" + ) + + f"{output_text}" + ) + self.multifunction_btn.setText(SubProcessDlg.tr("Cancel")) elif output_type == SubProcessTool.OutputType.ERROR: - self.info_label.setText("PyInstaller错误!") - self.text_browser.append(f"PyInstaller 子进程输出信息:{output_text}") - self.text_browser.append("请检查是否已经安装正确版本的 PyInstaller") - self.multifunction_btn.setText("关闭") + self.info_label.setText(SubProcessDlg.tr("PyInstaller Error!")) + self.text_browser.append( + SubProcessDlg.tr("PyInstaller subprocess output:") + f"{output_text}" + ) + self.text_browser.append( + SubProcessDlg.tr( + "Please check if you have installed " + "the correct version of PyInstaller or not." + ) + ) + self.multifunction_btn.setText(SubProcessDlg.tr("Close")) elif not isinstance(output_type, SubProcessTool.OutputType): - raise ValueError(f"不支持的输出类型:{output_type}") + raise ValueError(f"Unsupported output type: {output_type}") def closeEvent(self, event: QCloseEvent) -> None: """重写关闭事件,进行收尾清理 diff --git a/src/py2exe_gui/__main__.py b/src/py2exe_gui/__main__.py index d80a38b..24f2c58 100644 --- a/src/py2exe_gui/__main__.py +++ b/src/py2exe_gui/__main__.py @@ -10,14 +10,14 @@ import sys from pathlib import Path -from PySide6.QtCore import Slot +from PySide6.QtCore import QTranslator, Slot from PySide6.QtGui import QCloseEvent from PySide6.QtWidgets import QApplication -from .Constants import PyInstOpt +from .Constants import RUNTIME_INFO, PyInstOpt from .Core import Packaging, PackagingTask from .Resources import COMPILED_RESOURCES # noqa -from .Utilities import PyEnv, open_dir_in_explorer +from .Utilities import open_dir_in_explorer from .Widgets import MainWindow, SubProcessDlg @@ -27,17 +27,16 @@ class MainApp(MainWindow): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.current_pyenv: PyEnv self.packaging_task = PackagingTask(self) self.packager = Packaging(self) self.subprocess_dlg = SubProcessDlg(self) self._connect_slots() - self.status_bar.showMessage("就绪") + self.status_bar.showMessage(MainApp.tr("Ready.")) # def show(self): - # """仅供性能分析使用,切勿取消注释!!! + # """仅供分析启动性能使用,切勿取消注释!!! # """ # # super().show() @@ -88,16 +87,16 @@ class MainApp(MainWindow): """处理子进程窗口多功能按钮点击信号的槽""" btn_text = self.subprocess_dlg.multifunction_btn.text() - if btn_text == "取消": + if btn_text == SubProcessDlg.tr("Cancel"): self.packager.subprocess.abort_process() self.subprocess_dlg.close() - elif btn_text == "打开输出位置": + elif btn_text == SubProcessDlg.tr("Open Dist"): script_path: Path = self.packaging_task.using_option[ PyInstOpt.script_path ] dist_path = script_path.parent / "dist" open_dir_in_explorer(dist_path) - elif btn_text == "关闭": + elif btn_text == SubProcessDlg.tr("Close"): self.subprocess_dlg.close() subprocess_dlg.multifunction_btn.clicked.connect(handle_mul_btn_clicked) @@ -116,6 +115,13 @@ def main() -> None: """应用程序主入口函数,便于 Poetry 由此函数级入口构建启动脚本""" app = QApplication(sys.argv) + + # TODO 翻译机制待优化 + translator = QTranslator() + if RUNTIME_INFO.language_code == "zh_CN": + translator.load(":/i18n/zh_CN.qm") + app.installTranslator(translator) + window = MainApp() window.show() sys.exit(app.exec()) From 6a30d8c56ebf40c716d9648ed0936aa1cf4604da Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 21:54:28 +0800 Subject: [PATCH 5/7] Update README --- README.md | 13 ++++--------- README_zh.md | 8 ++++---- ...2exe-GUI_v0.3.1_mainwindow_screenshot_en.png | Bin 0 -> 26044 bytes 3 files changed, 8 insertions(+), 13 deletions(-) create mode 100644 docs/source/images/Py2exe-GUI_v0.3.1_mainwindow_screenshot_en.png diff --git a/README.md b/README.md index 8a89f9b..58b15f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Py2exe-GUI Logo](docs/source/images/py2exe-gui_logo_big.png) +![Py2exe-GUI Logo](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/py2exe-gui_logo_big.png)

Easy-to-use Python GUI packaging tool

@@ -24,9 +24,9 @@ English | 简体中文 Py2exe-GUI is an assist tool based on [PySide6](https://doc.qt.io/qtforpython/index.html), designed to provide a complete yet easy-to-use GUI for [PyInstaller](https://pyinstaller.org/). -![Screenshot](docs/source/images/Py2exe-GUI_v0.3.0_mainwindow_screenshot.png) +![Screenshot](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/Py2exe-GUI_v0.3.1_mainwindow_screenshot_en.png) -![Screenshot](docs/source/images/Py2exe-GUI_v0.2.0_screenshot.png) +![Screenshot](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/Py2exe-GUI_v0.2.0_screenshot.png) It has the following features: @@ -36,11 +36,6 @@ It has the following features: in each interpreter environment to be packaged. - Cross-platform, supports Windows, Linux and macOS. -> Note: As you can see, I am not an English speaker. Py2exe-GUI is currently only available in Simplified Chinese. -> However, I've reserved some interfaces in the code for internationalization, and the translation is slowly progressing. -> If you are interested in this project, you can star it. In a few months it will provide full English support and -> interfaces for translators to provide translations in more languages. - ## How to install > Note: Py2exe-GUI is still in the early stages of development, and the distributions provided are *beta versions*. @@ -112,7 +107,7 @@ the [checks](dev_scripts/check_funcs.py). ## License -![GPLv3](docs/source/images/gplv3-127x51.png) +![GPLv3](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/gplv3-127x51.png) Py2exe-GUI is licensed under the GPLv3 open source license, see the [LICENSE](LICENSE) file for details. diff --git a/README_zh.md b/README_zh.md index af22ba7..c2dc6a9 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,4 +1,4 @@ -![Py2exe-GUI Logo](docs/source/images/py2exe-gui_logo_big.png) +![Py2exe-GUI Logo](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/py2exe-gui_logo_big.png)

强大易用的 Python 图形界面打包工具

@@ -23,9 +23,9 @@ Py2exe-GUI 是一个基于 [PySide6](https://doc.qt.io/qtforpython/index.html) 开发的辅助工具,旨在为 [PyInstaller](https://pyinstaller.org/) 提供完整易用的图形化界面,方便用户进行 Python 项目的打包。 -![界面截图](docs/source/images/Py2exe-GUI_v0.3.0_mainwindow_screenshot.png) +![界面截图](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/Py2exe-GUI_v0.3.0_mainwindow_screenshot.png) -![界面截图](docs/source/images/Py2exe-GUI_v0.2.0_screenshot.png) +![界面截图](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/Py2exe-GUI_v0.2.0_screenshot.png) 有如下特性: @@ -101,7 +101,7 @@ request。请尽可能遵守原有的代码风格,并确保新增代码能通 ## 开源许可 -![GPLv3](docs/source/images/gplv3-127x51.png) +![GPLv3](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/gplv3-127x51.png) Py2exe-GUI 采用 GPLv3 开源许可证,详情请参见 [LICENSE](LICENSE) 文件。 diff --git a/docs/source/images/Py2exe-GUI_v0.3.1_mainwindow_screenshot_en.png b/docs/source/images/Py2exe-GUI_v0.3.1_mainwindow_screenshot_en.png new file mode 100644 index 0000000000000000000000000000000000000000..4b552093b2d2cf8a7f4dd24daef92d9d55d46153 GIT binary patch literal 26044 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 Date: Thu, 4 Jan 2024 21:59:23 +0800 Subject: [PATCH 6/7] Update README --- README.md | 2 +- dev_scripts/build.py | 35 ++--------------------------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 58b15f9..e6b78a0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Py2exe-GUI Logo](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/py2exe-gui_logo_big.png) +![Py2exe-GUI Logo](https://raw.githubusercontent.com/muziing/Py2exe-GUI/main/docs/source/images/py2exe-gui_logo_big.png)

Easy-to-use Python GUI packaging tool

diff --git a/dev_scripts/build.py b/dev_scripts/build.py index 65831f1..2d0e46d 100644 --- a/dev_scripts/build.py +++ b/dev_scripts/build.py @@ -2,7 +2,6 @@ """ import subprocess -from pathlib import Path from dev_scripts.check_funcs import ( check_license_statement, @@ -11,34 +10,7 @@ from dev_scripts.check_funcs import ( check_version_num, ) from dev_scripts.clear_cache import clear_pycache, clear_pyinstaller_dist -from dev_scripts.path_constants import PROJECT_ROOT, README_FILE_LIST, SRC_PATH - - -def process_md_images(md_file_list: list[Path]) -> None: - """处理 Markdown 文档中的图片链接 - - 在构建前替换为 GitHub 图床链接,在构建后替换回本地目录中的路径 - - :param md_file_list: Markdown 文件列表 - """ - - md_uri = "docs/source/images/" - github_uri = ( - "https://github.com/muziing/Py2exe-GUI/raw/main" + "/docs/source/images/" - ) - - for md_file in md_file_list: - with open(md_file, "r+", encoding="UTF-8") as f: - all_text = f.read() - if github_uri not in all_text: - print(f"将 {md_file} 中的本地图片路径替换为GitHub在线路径") - all_text_new = all_text.replace(md_uri, github_uri) - else: - print(f"将 {md_file} 中的GitHub在线路径替换为本地图片路径") - all_text_new = all_text.replace(github_uri, md_uri) - f.seek(0) - f.write(all_text_new) - # FIXME 会在文件尾部多出来莫名其妙的行 +from dev_scripts.path_constants import PROJECT_ROOT, SRC_PATH def export_requirements() -> int: @@ -73,7 +45,6 @@ def build_py2exe_gui() -> None: # 准备工作 clear_pyinstaller_dist(SRC_PATH) clear_pycache(SRC_PATH) - process_md_images(README_FILE_LIST) # compile_resources() export_requirements() print(f"pre-commit 检查完毕,返回码:{check_pre_commit()}。") @@ -89,13 +60,11 @@ def build_py2exe_gui() -> None: print(f"Poetry build 完毕,返回码:{result.returncode}。") finally: # 清理 - process_md_images(README_FILE_LIST) + pass else: print("有未通过的检查项,不进行构建") if __name__ == "__main__": - # process_md_images() - # compile_resources() # export_requirements() build_py2exe_gui() From 09f6533b8d6faf6f78e7f53ac946442e39314a04 Mon Sep 17 00:00:00 2001 From: muzing Date: Thu, 4 Jan 2024 22:04:06 +0800 Subject: [PATCH 7/7] Version `0.3.1` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新功能: - 初步实现国际化,实现自动切换界面语言功能; - 初步实现英文界面; --- poetry.lock | 49 +++++++++++++---------- pyproject.toml | 8 ++-- src/py2exe_gui/Constants/app_constants.py | 2 +- src/py2exe_gui/__init__.py | 2 +- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8f36dab..b200185 100644 --- a/poetry.lock +++ b/poetry.lock @@ -428,15 +428,20 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] [[package]] name = "pyinstaller-hooks-contrib" -version = "2023.11" +version = "2023.12" description = "Community maintained hooks for PyInstaller" optional = false python-versions = ">=3.7" files = [ - {file = "pyinstaller-hooks-contrib-2023.11.tar.gz", hash = "sha256:5dd7a8a054a65c19cdaa381cabcfbe76f44d5f88d18214b0c570a0cd139be77f"}, - {file = "pyinstaller_hooks_contrib-2023.11-py2.py3-none-any.whl", hash = "sha256:f2a75dac2968ec81f92dcd3768906f654fa4204bc496126ae8483e87a5d89602"}, + {file = "pyinstaller-hooks-contrib-2023.12.tar.gz", hash = "sha256:11a9d59d903723dd693e8c10b054f3ea1ecad390623c9fa527c731d715fc5b3f"}, + {file = "pyinstaller_hooks_contrib-2023.12-py2.py3-none-any.whl", hash = "sha256:6a601a0d783fa725327fc6ac712779475dc8979f639419c7fcd460dd8d0a6d2a"}, ] +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +packaging = ">=22.0" +setuptools = ">=42.0.0" + [[package]] name = "pyside6" version = "6.6.1" @@ -560,28 +565,28 @@ files = [ [[package]] name = "ruff" -version = "0.1.9" +version = "0.1.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, - {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, - {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, - {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, - {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, + {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"}, + {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"}, + {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"}, + {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"}, + {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"}, + {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"}, ] [[package]] @@ -684,4 +689,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.13" -content-hash = "a3e9cf4400a33e8958cc148c575a23017ff036c2dbd7a1185baadf8ad4d5e611" +content-hash = "73b33938229a7a1fa491c925f43bcd0c9d65b1ba4c0518d145420e254eaf8a9a" diff --git a/pyproject.toml b/pyproject.toml index 753e8f2..a7925fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,14 @@ [tool.poetry] name = "py2exe-gui" -version = "0.3.0" +version = "0.3.1" description = "GUI for PyInstaller, based on PySide6" keywords = ["PyInstaller", "GUI", "PySide6"] authors = ["muzing "] license = "GPL-3.0-or-later" readme = ["README.md", "README_zh.md"] repository = "https://github.com/muziing/Py2exe-GUI" -exclude = ["src/py2exe_gui/Resources/Icons", "src/py2exe_gui/Resources/Texts"] +exclude = ["src/py2exe_gui/Resources"] +include = ["src/py2exe_gui/Resources/COMPILED_RESOURCES.py"] classifiers = [ "Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", @@ -32,12 +33,11 @@ PySide6 = "^6.6.0" pyyaml = "^6.0.1" [tool.poetry.group.dev] - [tool.poetry.group.dev.dependencies] pre-commit = "^3.5.0" black = "^23.12.0" isort = "^5.13.0" -ruff = "^0.1.9" +ruff = "^0.1.11" mypy = "^1.8.0" pyinstaller = "^6.2.0" types-pyyaml = "^6.0.12.12" diff --git a/src/py2exe_gui/Constants/app_constants.py b/src/py2exe_gui/Constants/app_constants.py index ecce7d0..a578c30 100644 --- a/src/py2exe_gui/Constants/app_constants.py +++ b/src/py2exe_gui/Constants/app_constants.py @@ -24,7 +24,7 @@ class AppConstant: """应用程序级的常量""" NAME = "Py2exe-GUI" - VERSION = "0.3.0" + VERSION = "0.3.1" AUTHORS = ["muzing "] LICENSE = "GPL-3.0-or-later" HOME_PAGE = APP_URLs["HOME_PAGE"] diff --git a/src/py2exe_gui/__init__.py b/src/py2exe_gui/__init__.py index 0aec820..57f0d6f 100644 --- a/src/py2exe_gui/__init__.py +++ b/src/py2exe_gui/__init__.py @@ -7,4 +7,4 @@ designed to provide a complete yet easy-to-use GUI for PyInstaller. HomePage: https://github.com/muziing/Py2exe-GUI """ -__version__ = "0.3.0" +__version__ = "0.3.1"