From fba506d9d803ab3657a3c54a350edaa79c30018b Mon Sep 17 00:00:00 2001 From: Jokul Date: Fri, 24 Jul 2026 11:40:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF?= =?UTF-8?q?=E6=8C=81=EF=BC=8C=E8=8B=B1=E6=96=87=E6=BA=90=E4=B8=B2+?= =?UTF-8?q?=E4=B8=AD=E6=96=87=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全部44处qsTr源串改为英文,新建translations/jnetapplet_zh_CN.ts 中文翻译文件。CMake增加LinguistTools+qt_add_translation编译 .ts->.qm,init()按系统语言加载.qm并安装到qApp。 中文系统显示中文翻译,英文系统显示英文源串。 --- CMakeLists.txt | 18 +- docs/project-review.md | 5 +- package/components/AboutWindow.qml | 16 +- package/components/NetworkPopup.qml | 14 +- package/components/SettingsWindow.qml | 40 ++-- package/components/TrafficChartWindow.qml | 6 +- package/networkview.qml | 12 +- src/networkmonitorapplet.cpp | 14 ++ translations/jnetapplet_zh_CN.ts | 241 ++++++++++++++++++++++ 9 files changed, 318 insertions(+), 48 deletions(-) create mode 100644 translations/jnetapplet_zh_CN.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 321b4b3..37aa287 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) -find_package(Qt6 REQUIRED COMPONENTS Core Quick DBus Network) +find_package(Qt6 REQUIRED COMPONENTS Core Quick DBus Network LinguistTools) find_package(Dtk6 REQUIRED COMPONENTS Core) find_package(DDEShell REQUIRED) @@ -23,6 +23,17 @@ set_target_properties(space.jokul.JNetApplet PROPERTIES PREFIX "") # 将 project(VERSION) 暴露为编译宏,供 C++ version() 兜底使用 target_compile_definitions(space.jokul.JNetApplet PRIVATE PROJECT_VERSION="${PROJECT_VERSION}") +# 翻译文件:编译 .ts -> .qm,安装到插件目录的 translations/ 子目录 +# 设计原因:QML 中 qsTr() 源串为英文,中文翻译通过 .qm 文件在运行时加载 +set(TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translations/jnetapplet_zh_CN.ts) +qt_add_translation(QM_FILES ${TS_FILES}) +# 翻译文件安装路径,暴露为编译宏供 C++ 加载 +target_compile_definitions(space.jokul.JNetApplet PRIVATE + TRANSLATIONS_DIR="/usr/share/dde-shell/${PLUGIN_ID}/translations" +) +# 确保 .qm 在构建时生成(qt_add_translation 默认仅 install 时触发) +add_custom_target(translations ALL DEPENDS ${QM_FILES}) + # 头文件搜索路径指向 src/,使 #include "networkmonitorapplet.h" 可被外部解析 target_include_directories(space.jokul.JNetApplet PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src @@ -51,4 +62,7 @@ install(FILES package/networkview.qml DESTINATION /usr/share/dde-shell/${PLUGIN_ # 安装 components 子目录下所有 QML 组件 # 设计原因:拆分出的子组件需随主 QML 一起安装到 dde-shell 插件目录, # 否则 networkview.qml 的 import "components" 在运行时找不到类型 -install(DIRECTORY package/components DESTINATION /usr/share/dde-shell/${PLUGIN_ID}) \ No newline at end of file +install(DIRECTORY package/components DESTINATION /usr/share/dde-shell/${PLUGIN_ID}) + +# 安装编译后的翻译文件 +install(FILES ${QM_FILES} DESTINATION /usr/share/dde-shell/${PLUGIN_ID}/translations) \ No newline at end of file diff --git a/docs/project-review.md b/docs/project-review.md index d4a4f2b..bbb22b1 100644 --- a/docs/project-review.md +++ b/docs/project-review.md @@ -75,8 +75,9 @@ ~~项目结构只列 AboutWindow.qml,功能特性未提及设置窗口/字体颜色/流量波动图/IPv6/深色模式,README 写"从弹窗卸载"实际在设置窗口。~~ 已更新 README.md 和 AGENTS.md 的功能特性列表、项目结构(补全 6 个组件)、C++ Backend 属性列表(移除 interfaceStats,新增 ipv6Address/version/textColor/speedHistory 等)。 -**16. `qsTr()` 源字符串为中文,无翻译基础设施** -QML 中 `qsTr("网络速度监控")` 等以中文为源串,但项目无 `.ts` 翻译文件、无 `lupdate`/`lrelease` 构建步骤、C++ 无翻译加载逻辑。`qsTr()` 实质为空操作。若仅面向中文用户可接受;若计划国际化,需补全 i18n 基础设施并以英文为源串。 +**16. ~~`qsTr()` 源字符串为中文,无翻译基础设施~~ ✅ 已修复** +~~QML 中 `qsTr()` 以中文为源串,无 `.ts` 翻译文件、无 `lupdate`/`lrelease` 构建步骤、C++ 无翻译加载逻辑。~~ +已将全部 44 处 `qsTr` 源串改为英文,新建 `translations/jnetapplet_zh_CN.ts` 中文翻译文件。CMake 增加 `LinguistTools` + `qt_add_translation` 编译 `.ts` -> `.qm`,`init()` 中按系统语言加载 `.qm` 并安装到 `qApp`。 **17. C++ 后端无单元测试** 项目无任何测试。`calculateSpeed`、`readNetworkStats` 的正则解析、`isPhysicalInterface`、`niceCeil`(QML)等纯逻辑函数适合且应该有单元测试覆盖。 diff --git a/package/components/AboutWindow.qml b/package/components/AboutWindow.qml index 26a5396..986c0f2 100644 --- a/package/components/AboutWindow.qml +++ b/package/components/AboutWindow.qml @@ -83,7 +83,7 @@ Window { anchors.left: parent.left anchors.leftMargin: 16 anchors.verticalCenter: parent.verticalCenter - text: qsTr("关于") + text: qsTr("About") font.pixelSize: 15 font.weight: Font.Bold color: root.textPrimary @@ -130,7 +130,7 @@ Window { spacing: 2 Text { - text: qsTr("网络速度监控") + text: qsTr("Network Speed Monitor") font.pixelSize: 18 font.weight: Font.Bold color: root.textPrimary @@ -179,7 +179,7 @@ Window { spacing: 0 Text { - text: qsTr("版本") + text: qsTr("Version") font.pixelSize: 12 color: "#0081FF" Layout.preferredWidth: 56 @@ -200,7 +200,7 @@ Window { spacing: 0 Text { - text: qsTr("作者") + text: qsTr("Author") font.pixelSize: 12 color: "#0081FF" Layout.preferredWidth: 56 @@ -221,7 +221,7 @@ Window { spacing: 0 Text { - text: qsTr("描述") + text: qsTr("Description") font.pixelSize: 12 color: "#0081FF" Layout.preferredWidth: 56 @@ -229,7 +229,7 @@ Window { } Text { - text: qsTr("监控网络速度和流量") + text: qsTr("Monitor network speed and traffic") font.pixelSize: 12 color: root.textPrimary Layout.fillWidth: true @@ -243,7 +243,7 @@ Window { spacing: 0 Text { - text: qsTr("仓库") + text: qsTr("Repository") font.pixelSize: 12 color: "#0081FF" Layout.preferredWidth: 56 @@ -281,7 +281,7 @@ Window { Text { id: copiedHintText anchors.centerIn: parent - text: qsTr("已复制") + text: qsTr("Copied") font.pixelSize: 10 color: "#22A34A" } diff --git a/package/components/NetworkPopup.qml b/package/components/NetworkPopup.qml index 8eecda3..9f3b157 100644 --- a/package/components/NetworkPopup.qml +++ b/package/components/NetworkPopup.qml @@ -95,7 +95,7 @@ Control { spacing: 6 Text { - text: qsTr("网络速度监控") + text: qsTr("Network Speed Monitor") font.pixelSize: 15 font.weight: Font.Bold color: popup.primaryText @@ -126,7 +126,7 @@ Control { } Text { - text: popup.ipAddress ? qsTr("IPv4:") + popup.ipAddress : "" + text: popup.ipAddress ? qsTr("IPv4: ") + popup.ipAddress : "" font.pixelSize: 13 color: popup.secondaryText visible: popup.ipAddress !== "" @@ -135,7 +135,7 @@ Control { // IPv6 全球地址行,无 IPv6 时隐藏不占位 Text { - text: popup.ipv6Address ? qsTr("IPv6:") + popup.ipv6Address : "" + text: popup.ipv6Address ? qsTr("IPv6: ") + popup.ipv6Address : "" font.pixelSize: 13 color: popup.secondaryText visible: popup.ipv6Address !== "" @@ -187,7 +187,7 @@ Control { } Text { - text: qsTr("下载") + text: qsTr("Download") font.pixelSize: 11 color: popup.secondaryText Layout.alignment: Qt.AlignHCenter @@ -244,7 +244,7 @@ Control { } Text { - text: qsTr("上传") + text: qsTr("Upload") font.pixelSize: 11 color: popup.secondaryText Layout.alignment: Qt.AlignHCenter @@ -279,7 +279,7 @@ Control { Item { Layout.fillWidth: true } Text { - text: qsTr("总计") + text: qsTr("Total") font.pixelSize: 11 font.weight: Font.Bold color: popup.secondaryText @@ -456,7 +456,7 @@ Control { } Text { - text: qsTr("未检测到网络接口") + text: qsTr("No network interface detected") font.pixelSize: 12 color: popup.secondaryText Layout.alignment: Qt.AlignHCenter diff --git a/package/components/SettingsWindow.qml b/package/components/SettingsWindow.qml index e263b09..cc0eece 100644 --- a/package/components/SettingsWindow.qml +++ b/package/components/SettingsWindow.qml @@ -51,7 +51,7 @@ Window { readonly property color selText: isDarkMode ? "#64B5F6" : "#1565C0" // 卸载按钮文字:复制命令后临时显示提示,定时器到期后还原 - property string uninstallButtonText: qsTr("卸载插件") + property string uninstallButtonText: qsTr("Uninstall Plugin") width: 350 height: 390 @@ -70,15 +70,15 @@ Window { // 设计原因:用户面对多个网口时难以仅凭 enp3s0/wlp3s0 等命名判断用途, // 加一行类型说明(有线/无线/VPN 等)降低认知负担 function interfaceDescription(name) { - if (name === "lo") return qsTr("本地回环") - if (name === "Meta") return qsTr("虚拟接口") - if (/^enp|^eth/.test(name)) return qsTr("有线网络") - if (/^wlp|^wlan/.test(name)) return qsTr("无线网络") - if (/^docker|^veth/.test(name)) return qsTr("容器网络") - if (/^br/.test(name)) return qsTr("桥接") + if (name === "lo") return qsTr("Loopback") + if (name === "Meta") return qsTr("Virtual Interface") + if (/^enp|^eth/.test(name)) return qsTr("Wired Network") + if (/^wlp|^wlan/.test(name)) return qsTr("Wireless Network") + if (/^docker|^veth/.test(name)) return qsTr("Container Network") + if (/^br/.test(name)) return qsTr("Bridge") if (/^tun|^tap/.test(name)) return qsTr("VPN") - if (/^virbr/.test(name)) return qsTr("虚拟桥接") - return qsTr("其他") + if (/^virbr/.test(name)) return qsTr("Virtual Bridge") + return qsTr("Other") } // 根据接口名返回类型图标(Unicode 符号),用于网络接口列表每行左侧 @@ -139,7 +139,7 @@ Window { anchors.left: parent.left anchors.leftMargin: 16 anchors.verticalCenter: parent.verticalCenter - text: qsTr("设置") + text: qsTr("Settings") font.pixelSize: 15 font.weight: Font.Bold color: root.textPrimary @@ -193,7 +193,7 @@ Window { // 网络接口选择区 Text { - text: qsTr("网络接口") + text: qsTr("Network Interface") font.pixelSize: 13 font.weight: Font.Bold color: root.textSecondary @@ -346,7 +346,7 @@ Window { // 无接口提示:在卡片内居中 Text { anchors.centerIn: parent - text: qsTr("未检测到网络接口") + text: qsTr("No network interface detected") font.pixelSize: 12 color: root.textTertiary visible: networkInterfaces.length === 0 @@ -357,7 +357,7 @@ Window { // 设计原因:用户选择的颜色通过 applet.textColor 持久化到 // ~/.config/jnetapplet/settings.ini,重启 dde-shell 后仍生效 Text { - text: qsTr("字体颜色") + text: qsTr("Font Color") font.pixelSize: 13 font.weight: Font.Bold color: root.textSecondary @@ -380,7 +380,7 @@ Window { Layout.fillWidth: true Layout.preferredHeight: 40 // 提示状态时背景和边框变绿 - readonly property bool isStatus: uninstallButtonText !== qsTr("卸载插件") + readonly property bool isStatus: uninstallButtonText !== qsTr("Uninstall Plugin") color: isStatus ? Qt.rgba(22/255, 163/255, 74/255, 0.08) : (uninstallMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : Qt.rgba(220/255, 38/255, 38/255, 0.08)) @@ -443,7 +443,7 @@ Window { anchors.margins: 20 Text { - text: qsTr("警告") + text: qsTr("Warning") font.pixelSize: 16 font.weight: Font.Bold color: accentColor @@ -451,7 +451,7 @@ Window { } Text { - text: qsTr("以下命令用于卸载插件并重启 dde-shell,请复制到终端中执行:") + text: qsTr("The following command uninstalls the plugin and restarts dde-shell. Please copy and run it in terminal:") font.pixelSize: 12 color: root.textPrimary Layout.alignment: Qt.AlignHCenter @@ -496,7 +496,7 @@ Window { Text { anchors.centerIn: parent - text: qsTr("取消") + text: qsTr("Cancel") font.pixelSize: 13 color: root.textPrimary } @@ -519,7 +519,7 @@ Window { Text { anchors.centerIn: parent - text: qsTr("复制命令") + text: qsTr("Copy Command") font.pixelSize: 13 font.weight: Font.Medium color: "white" @@ -536,7 +536,7 @@ Window { clipboardHelper.copy() uninstallConfirmDialog.close() // 卸载按钮文字临时替换为复制成功提示,5 秒后还原 - uninstallButtonText = qsTr("卸载命令已复制到剪贴板,请在终端中粘贴执行") + uninstallButtonText = qsTr("Uninstall command copied to clipboard. Please paste and run it in terminal.") statusHideTimer.start() } } @@ -557,6 +557,6 @@ Window { Timer { id: statusHideTimer interval: 5000 - onTriggered: uninstallButtonText = qsTr("卸载插件") + onTriggered: uninstallButtonText = qsTr("Uninstall Plugin") } } diff --git a/package/components/TrafficChartWindow.qml b/package/components/TrafficChartWindow.qml index 0904359..1eb4176 100644 --- a/package/components/TrafficChartWindow.qml +++ b/package/components/TrafficChartWindow.qml @@ -177,7 +177,7 @@ Window { anchors.left: parent.left anchors.leftMargin: 16 anchors.verticalCenter: parent.verticalCenter - text: qsTr("流量波动图") + text: qsTr("Traffic Chart") font.pixelSize: 15 font.weight: Font.Bold color: root.textPrimary @@ -421,7 +421,7 @@ Window { ctx.font = "13px sans-serif" ctx.textAlign = "center" ctx.textBaseline = "middle" - ctx.fillText(qsTr("暂无历史数据"), pl + pw / 2, pt + ph / 2) + ctx.fillText(qsTr("No history data"), pl + pw / 2, pt + ph / 2) return } @@ -578,7 +578,7 @@ Window { Text { anchors.centerIn: parent text: root.hoverHint.length > 0 - ? root.hoverHint : qsTr("鼠标移到曲线上查看详情") + ? root.hoverHint : qsTr("Hover over the curve for details") font.pixelSize: 11 color: root.hoverHint.length > 0 ? root.textPrimary : root.textTertiary } diff --git a/package/networkview.qml b/package/networkview.qml index 59ab269..408948b 100644 --- a/package/networkview.qml +++ b/package/networkview.qml @@ -222,9 +222,9 @@ AppletItem { id: toolTip text: root.ready ? (root.activeInterface - + " · IPv4:" + (root.ipAddress || qsTr("无")) - + (root.ipv6Address ? "\nIPv6:" + root.ipv6Address : "")) - : qsTr("未检测到网络接口") + + " · " + qsTr("IPv4: ") + (root.ipAddress || qsTr("None")) + + (root.ipv6Address ? "\n" + qsTr("IPv6: ") + root.ipv6Address : "")) + : qsTr("No network interface detected") toolTipX: DockPanelPositioner.x toolTipY: DockPanelPositioner.y } @@ -300,7 +300,7 @@ AppletItem { id: contextMenu Platform.MenuItem { - text: qsTr("设置") + text: qsTr("Settings") onTriggered: { settingsWindow.show() settingsWindow.raise() @@ -310,7 +310,7 @@ AppletItem { // 流量波动图:屏幕居中独立窗口,展示活动接口最近 30 分钟网速趋势 Platform.MenuItem { - text: qsTr("流量波动图") + text: qsTr("Traffic Chart") onTriggered: { trafficChartWindow.show() trafficChartWindow.raise() @@ -321,7 +321,7 @@ AppletItem { Platform.MenuSeparator {} Platform.MenuItem { - text: qsTr("关于") + text: qsTr("About") onTriggered: { aboutWindow.show() aboutWindow.raise() diff --git a/src/networkmonitorapplet.cpp b/src/networkmonitorapplet.cpp index bf7e1b5..7451b72 100644 --- a/src/networkmonitorapplet.cpp +++ b/src/networkmonitorapplet.cpp @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include DS_BEGIN_NAMESPACE @@ -65,6 +68,17 @@ bool NetworkMonitorApplet::load() bool NetworkMonitorApplet::init() { + // 加载翻译文件:根据系统语言加载对应的 .qm 文件 + // 设计原因:QML 源串为英文,中文翻译通过 .qm 文件在运行时加载。 + // 翻译上下文为 QML 文件名(如 "networkview"、"AboutWindow"), + // 安装到 QCoreApplication 后 qsTr() 自动查找匹配。 + QTranslator *translator = new QTranslator(this); + const QString locale = QLocale::system().name(); + if (translator->load(QStringLiteral("jnetapplet_%1.qm").arg(locale), + QStringLiteral(TRANSLATIONS_DIR))) { + qApp->installTranslator(translator); + } + detectInterfaces(); // 启动定时刷新 diff --git a/translations/jnetapplet_zh_CN.ts b/translations/jnetapplet_zh_CN.ts new file mode 100644 index 0000000..48e7bed --- /dev/null +++ b/translations/jnetapplet_zh_CN.ts @@ -0,0 +1,241 @@ + + + + + AboutWindow + + + About + 关于 + + + + Network Speed Monitor + 网络速度监控 + + + + Version + 版本 + + + + Author + 作者 + + + + Description + 描述 + + + + Monitor network speed and traffic + 监控网络速度和流量 + + + + Repository + 仓库 + + + + Copied + 已复制 + + + + NetworkPopup + + + Network Speed Monitor + 网络速度监控 + + + + IPv4: + IPv4: + + + + IPv6: + IPv6: + + + + Download + 下载 + + + + Upload + 上传 + + + + Total + 总计 + + + + No network interface detected + 未检测到网络接口 + + + + SettingsWindow + + + + + Uninstall Plugin + 卸载插件 + + + + Loopback + 本地回环 + + + + Virtual Interface + 虚拟接口 + + + + Wired Network + 有线网络 + + + + Wireless Network + 无线网络 + + + + Container Network + 容器网络 + + + + Bridge + 桥接 + + + + VPN + VPN + + + + Virtual Bridge + 虚拟桥接 + + + + Other + 其他 + + + + Settings + 设置 + + + + Network Interface + 网络接口 + + + + No network interface detected + 未检测到网络接口 + + + + Font Color + 字体颜色 + + + + Warning + 警告 + + + + The following command uninstalls the plugin and restarts dde-shell. Please copy and run it in terminal: + 以下命令用于卸载插件并重启 dde-shell,请复制到终端中执行: + + + + Cancel + 取消 + + + + Copy Command + 复制命令 + + + + Uninstall command copied to clipboard. Please paste and run it in terminal. + 卸载命令已复制到剪贴板,请在终端中粘贴执行 + + + + TrafficChartWindow + + + Traffic Chart + 流量波动图 + + + + No history data + 暂无历史数据 + + + + Hover over the curve for details + 鼠标移到曲线上查看详情 + + + + networkview + + + IPv4: + IPv4: + + + + None + + + + + IPv6: + IPv6: + + + + No network interface detected + 未检测到网络接口 + + + + Settings + 设置 + + + + Traffic Chart + 流量波动图 + + + + About + 关于 + + +