feat(i18n): 新增中英文国际化支持,语言下拉框即时切换

This commit is contained in:
2026-07-28 15:18:58 +08:00
parent 935665e7e8
commit 015f2636b3
5 changed files with 446 additions and 116 deletions
+50
View File
@@ -0,0 +1,50 @@
"""国际化模块:提供 t(key, **kwargs) 翻译函数,支持语言切换。
模块导入时自动加载 zh_CN 作为默认语言。
通过 switch(locale) 切换语言,t() 返回当前语言的翻译文本。
"""
from __future__ import annotations
import json
from pathlib import Path
_I18N_DIR = Path(__file__).parent
_translations: dict[str, str] = {}
_current_locale: str = "zh_CN"
def load(locale: str) -> None:
"""加载指定语言的翻译文件。"""
global _translations, _current_locale
_current_locale = locale
path = _I18N_DIR / f"{locale}.json"
if path.exists():
_translations = json.loads(path.read_text(encoding="utf-8"))
else:
_translations = {}
def switch(locale: str) -> None:
"""切换当前语言并返回新语言代码。"""
load(locale)
def current_locale() -> str:
"""返回当前语言代码。"""
return _current_locale
def t(key: str, **kwargs: object) -> str:
"""获取翻译文本,支持 {name} 插值。
如果 key 不存在,返回 key 本身作为回退。
"""
text = str(_translations.get(key, key))
if kwargs:
text = text.format(**{k: v for k, v in kwargs.items()})
return text
# 模块导入时自动加载默认中文翻译
load("zh_CN")
+112
View File
@@ -0,0 +1,112 @@
{
"_lang": "English",
"gui.file_group.title": "Input 3MF Files",
"gui.file_group.table.header.order": "Order",
"gui.file_group.table.header.file": "3MF File",
"gui.file_group.table.header.copies": "Copies",
"gui.file_group.table.header.time": "Time",
"gui.file_group.table.header.filament": "Filament",
"gui.file_group.table.tooltip": "Drag .3mf files or folders here. Folders add all top-level .3mf files.",
"gui.file_group.table.move_up": "Move up",
"gui.file_group.table.move_down": "Move down",
"gui.file_group.table.tooltip_base_plates": "Base plates: {count}",
"gui.file_group.table.tooltip_base_time": "Base time: {time}",
"gui.file_group.table.tooltip_base_filament": "Base filament: {filament}",
"gui.file_group.total_summary": "Total: {plates} plate(s) | Time: {time} | Filament: {filament}",
"gui.file_group.total_summary.empty": "Total: 0 plates | Time: 0s | Filament: 0.00 g",
"gui.button.add_3mf": "Add 3MF",
"gui.button.remove": "Remove",
"gui.button.remove_all": "Remove All",
"gui.button.apply_copies": "Apply Default Copies to Selected",
"gui.button.build": "Build 3MF",
"gui.preview.title": "Selected Thumbnail",
"gui.preview.select_hint": "Select an input file",
"gui.preview.no_image": "No preview image",
"gui.preview.unavailable": "Preview unavailable",
"gui.options.group.title": "Packing Options",
"gui.options.swap_gcode.label": "Swap G-code",
"gui.options.refresh": "Refresh",
"gui.options.open_folder": "Open Folder",
"gui.options.default_copies": "Default copies for new inputs",
"gui.options.bed_cooldown": "Wait for bed cooldown",
"gui.options.bed_cooldown.label": "Bed cooldown",
"gui.options.bed_cooldown.suffix": "°C",
"gui.options.eject_wait": "Wait after swap",
"gui.options.eject_wait.label": "Wait after swap",
"gui.options.wait_suffix": "sec",
"gui.options.plate_number": "Show current plate in the hundreds digit of remaining time",
"gui.options.plate_number.label": "Plate number",
"gui.options.final_swap": "Run swap G-code after the last plate",
"gui.options.final_swap.label": "Final swap",
"gui.options.gcode_patches": "Apply editable G-code patches",
"gui.options.gcode_patches.label": "G-code patches",
"gui.options.gcode_patches.tooltip": "Uses gcode_patches.ini",
"gui.options.open_config": "Open Config",
"gui.options.metadata.label": "3MF metadata",
"gui.options.metadata.source": "Keep source prediction and weight",
"gui.options.metadata.sum": "Sum prediction and filament",
"gui.options.zip_compression.label": "ZIP compression",
"gui.options.zip_compression.level_format": "Level {level}",
"gui.options.zip_compression.tooltip": "zlib-ng Deflate compression level for the output 3MF.",
"gui.options.batch_mode": "Individual batch mode",
"gui.options.batch_mode.label": "Batch mode",
"gui.options.batch_mode.tooltip": "Build each input row as a separate output file, using that row's copy count.",
"gui.options.clear_after_build": "Clear input list after successful build",
"gui.options.clear_after_build.label": "After build",
"gui.options.skip_duplicates": "Skip duplicate file paths when adding inputs",
"gui.options.input_handling": "Input handling",
"gui.options.language": "Language",
"gui.output.group.title": "Output",
"gui.output.dir.label": "Output directory",
"gui.output.dir.placeholder": "Leave empty to write next to the input file",
"gui.output.dir.browse": "Browse",
"gui.output.filename_rule": "Output filename rule",
"gui.output.filename_rule.placeholder": "Use tokens such as {source}, {sources}, {plates}, {copies}, {date}, {time}",
"gui.output.filename_rule.help": "?",
"gui.output.filename_rule.help.tooltip": "Show filename token help",
"gui.output.preview": "Preview",
"gui.output.batch_preview": "{first} | {count} output file(s)",
"gui.output.printer.send": "Send to printer after build",
"gui.output.printer.ip": "IP Address",
"gui.output.printer.access_code": "Access Code",
"gui.output.printer.serial": "Serial",
"gui.output.printer.send.tooltip": "Send the generated 3MF to an A1 printer over LAN and start printing",
"gui.dialog.individual_batch.title": "Individual Batch Mode",
"gui.dialog.individual_batch.text": "Individual batch mode treats every input row as a separate build.\n\nExample: if you add 20 single-plate 3MF files and set copies to 5, Build 3MF will create 20 separate packed files. Each output contains only that source file repeated 5 times.\n\nThis is useful for quickly batch-converting many independent 3MF jobs into multi-copy SwapMod packs. It does not combine all input files into one 3MF.",
"gui.dialog.filename_help.title": "Output Filename Rule",
"gui.dialog.filename_help.text": "Available filename tokens:\n\n{source} - First input file stem, without .3mf\n{sources} - Source name summary. For multiple different inputs, it becomes first_source_and_N_more\n{plates} - Total plate count in the output\n{copies} - Total copy count used for the output\n{date} - Current date as YYYYMMDD\n{time} - Current time as HHMMSS\n\nDefault rule:\n{plates}-Plates-{sources}.3mf\n\nIn individual batch mode, these tokens are calculated separately for each input row.",
"gui.dialog.warning.title": "A1 Swap Mod Packer",
"gui.dialog.no_files": "Please add at least one 3MF file.",
"gui.dialog.no_swap_gcode": "Please put at least one swap G-code file in the swap_gcode folder and select it.",
"gui.dialog.patch_config_not_found": "Patch config does not exist yet:\n{path}",
"gui.dialog.batch_partial_success": "Built {success} file(s), but {failed} file(s) failed.\n\n",
"gui.dialog.choose_output_dir": "Choose output directory",
"gui.dialog.add_files_title": "Add 3MF files",
"gui.dialog.file_filter.3mf": "3MF files (*.3mf)",
"gui.dialog.file_filter.all": "All files (*)",
"gui.toast.combined_success": "The packed 3MF file was created.",
"gui.toast.batch_success": "Created {count} packed 3MF file(s).",
"gui.toast.send_success": "Sent to printer: {name}",
"gui.toast.send_failed": "Failed to send to printer: {error}",
"gui.log.added_files": "Added {added} input file(s). Skipped {skipped}.",
"gui.log.skipped_file": "Skipped {path}: {reason}",
"gui.log.no_swap_files": "No swap G-code files found in {dir}",
"gui.log.building_individual": "Building {count} individual output(s) with {workers} worker(s).",
"gui.log.output": "Output: {path}",
"gui.log.plates": "Plates: {count}",
"gui.log.time": "Estimated source time: {time}",
"gui.log.filament": "Estimated source filament: {filament}",
"gui.log.md5": "G-code MD5: {md5}",
"gui.log.build_error": "Error building {path}: {error}",
"gui.log.printer_missing": "Error: bambulabs_api not installed, run: pip install bambulabs_api",
"format.unknown": "Unknown"
}
+112
View File
@@ -0,0 +1,112 @@
{
"_lang": "简体中文",
"gui.file_group.title": "输入 3MF 文件",
"gui.file_group.table.header.order": "顺序",
"gui.file_group.table.header.file": "3MF 文件",
"gui.file_group.table.header.copies": "份数",
"gui.file_group.table.header.time": "时间",
"gui.file_group.table.header.filament": "耗材",
"gui.file_group.table.tooltip": "将 .3mf 文件或文件夹拖放到此处。文件夹将添加所有顶层 .3mf 文件。",
"gui.file_group.table.move_up": "上移",
"gui.file_group.table.move_down": "下移",
"gui.file_group.table.tooltip_base_plates": "基础板数:{count}",
"gui.file_group.table.tooltip_base_time": "基础时间:{time}",
"gui.file_group.table.tooltip_base_filament": "基础耗材:{filament}",
"gui.file_group.total_summary": "合计:{plates} 板 | 时间:{time} | 耗材:{filament}",
"gui.file_group.total_summary.empty": "合计:0 板 | 时间:0s | 耗材:0.00 g",
"gui.button.add_3mf": "添加 3MF",
"gui.button.remove": "移除",
"gui.button.remove_all": "全部移除",
"gui.button.apply_copies": "应用默认份数至选中行",
"gui.button.build": "构建 3MF",
"gui.preview.title": "选中文件的缩略图",
"gui.preview.select_hint": "选择一个输入文件",
"gui.preview.no_image": "无预览图",
"gui.preview.unavailable": "预览不可用",
"gui.options.group.title": "打包选项",
"gui.options.swap_gcode.label": "换盘 G-code",
"gui.options.refresh": "刷新",
"gui.options.open_folder": "打开文件夹",
"gui.options.default_copies": "新输入的默认份数",
"gui.options.bed_cooldown": "等待热床降温",
"gui.options.bed_cooldown.label": "热床降温",
"gui.options.bed_cooldown.suffix": "°C",
"gui.options.eject_wait": "换盘后等待",
"gui.options.eject_wait.label": "换盘后等待时间",
"gui.options.wait_suffix": "秒",
"gui.options.plate_number": "在剩余时间的百位显示当前板号",
"gui.options.plate_number.label": "剩余时间板号",
"gui.options.final_swap": "最后一块盘后也执行换盘 G-code",
"gui.options.final_swap.label": "最后换盘",
"gui.options.gcode_patches": "应用可编辑的 G-code 补丁",
"gui.options.gcode_patches.label": "G-code 补丁",
"gui.options.gcode_patches.tooltip": "使用 gcode_patches.ini",
"gui.options.open_config": "打开配置文件",
"gui.options.metadata.label": "3MF 元数据",
"gui.options.metadata.source": "保留原始预测和重量",
"gui.options.metadata.sum": "累加预测和耗材用量",
"gui.options.zip_compression.label": "ZIP 压缩",
"gui.options.zip_compression.level_format": "级别 {level}",
"gui.options.zip_compression.tooltip": "输出 3MF 的 zlib-ng Deflate 压缩级别。",
"gui.options.batch_mode": "独立批处理模式",
"gui.options.batch_mode.label": "批处理模式",
"gui.options.batch_mode.tooltip": "将每行输入构建为独立的输出文件,使用该行的份数。",
"gui.options.clear_after_build": "构建成功后清空输入列表",
"gui.options.clear_after_build.label": "构建成功后",
"gui.options.skip_duplicates": "添加输入时跳过重复文件路径",
"gui.options.input_handling": "输入处理",
"gui.options.language": "语言",
"gui.output.group.title": "输出",
"gui.output.dir.label": "输出目录",
"gui.output.dir.placeholder": "留空则写入输入文件所在目录",
"gui.output.dir.browse": "浏览",
"gui.output.filename_rule": "输出文件名规则",
"gui.output.filename_rule.placeholder": "可使用 {source}、{sources}、{plates}、{copies}、{date}、{time} 等标记",
"gui.output.filename_rule.help": "?",
"gui.output.filename_rule.help.tooltip": "显示文件名标记帮助",
"gui.output.preview": "预览",
"gui.output.batch_preview": "{first} | {count} 个输出文件",
"gui.output.printer.send": "构建后发送至打印机",
"gui.output.printer.ip": "IP 地址",
"gui.output.printer.access_code": "访问码",
"gui.output.printer.serial": "序列号",
"gui.output.printer.send.tooltip": "通过局域网将生成的 3MF 发送到 A1 打印机并开始打印",
"gui.dialog.individual_batch.title": "独立批处理模式",
"gui.dialog.individual_batch.text": "独立批处理模式将每一行输入作为独立的构建任务。\n\n示例:如果你添加了 20 个单板 3MF 文件并设置份数为 5,点击\"构建 3MF\"将创建 20 个独立的打包文件。每个输出文件仅包含该源文件重复 5 次的内容。\n\n这个功能适用于快速将大量独立的 3MF 作业批量转换为多份数的 SwapMod 包。它不会将所有输入文件合并到一个 3MF 中。",
"gui.dialog.filename_help.title": "输出文件名规则",
"gui.dialog.filename_help.text": "可用的文件名标记:\n\n{source} — 第一个输入文件的文件名(不含 .3mf)\n{sources} — 源文件名称摘要。多个不同输入时,显示为 first_source_and_N_more\n{plates} — 输出中的总板数\n{copies} — 输出中使用的总份数\n{date} — 当前日期,格式 YYYYMMDD\n{time} — 当前时间,格式 HHMMSS\n\n默认规则:\n{plates}-Plates-{sources}.3mf\n\n在独立批处理模式下,这些标记会针对每行输入单独计算。",
"gui.dialog.warning.title": "A1 Swap Mod Packer",
"gui.dialog.no_files": "请至少添加一个 3MF 文件。",
"gui.dialog.no_swap_gcode": "请在 swap_gcode 文件夹中放入至少一个换盘 G-code 文件并选择它。",
"gui.dialog.patch_config_not_found": "补丁配置文件不存在:{path}",
"gui.dialog.batch_partial_success": "已构建 {success} 个文件,但有 {failed} 个文件失败。\n\n",
"gui.dialog.choose_output_dir": "选择输出目录",
"gui.dialog.add_files_title": "添加 3MF 文件",
"gui.dialog.file_filter.3mf": "3MF 文件 (*.3mf)",
"gui.dialog.file_filter.all": "所有文件 (*)",
"gui.toast.combined_success": "已创建打包的 3MF 文件。",
"gui.toast.batch_success": "已创建 {count} 个打包的 3MF 文件。",
"gui.toast.send_success": "已发送至打印机:{name}",
"gui.toast.send_failed": "发送至打印机失败:{error}",
"gui.log.added_files": "已添加 {added} 个输入文件。跳过 {skipped} 个。",
"gui.log.skipped_file": "已跳过 {path}{reason}",
"gui.log.no_swap_files": "在 {dir} 中未找到换盘 G-code 文件",
"gui.log.building_individual": "正在使用 {workers} 个工作线程构建 {count} 个独立输出。",
"gui.log.output": "输出:{path}",
"gui.log.plates": "板数:{count}",
"gui.log.time": "预估源文件时间:{time}",
"gui.log.filament": "预估源文件耗材:{filament}",
"gui.log.md5": "G-code MD5{md5}",
"gui.log.build_error": "构建 {path} 时出错:{error}",
"gui.log.printer_missing": "错误:未安装 bambulabs_api,请执行 pip install bambulabs_api",
"format.unknown": "未知"
}