refactor: 按 gpui-kit 官方指南对齐(locales 拆分/render 无突变/动作统一)
skill 安装:npx skills add 因网络不可用,改为 gh-proxy 镜像克隆仓库后 手动覆盖 ~/.agents/skills 与 ~/.zcode/skills 两处安装(与官方最新一致)。 语言包按语言分文件(用户要求): - locales/ui.yml(合并格式)→ locales/en.yml + locales/zh-CN.yml (rust-i18n 以文件名 stem 识别 locale,扁平 "ns.key": "value") - i18n.rs 测试适配双文件:键集合一致性 + 占位符两侧一致强制保留 对齐 coding-guides / design-guides / auto-update: - render 无突变:分组过滤悬空复位从 render 移到 delete_connection/ save_connection 的 action 路径(normalize_group_filter) - 消除 UI 层 hsla 字面量:消息色条改 when_some 条件上色;色板描边 从白 60% 改主题底色(双主题下均为「与背景的缝隙」语义) - 动作统一:标题栏菜单的资源监控/导出/导入/设置改 dispatch_action, 与快捷键共享同一 Action(菜单/快捷键单一命令策略) - auto-update 对齐官方指南:自动检查加 24h 节流(.last-update-check 时间戳),手动检查更新不受限 保留(记录豁免理由): - 手写 collapsible_section:官方 Accordion 非受控(内部索引状态), 替代会丢「已配置默认展开」的受控行为 - 自研 update/:官方无内置 updater,指南推荐 self_update+应用自有 状态机,现有实现(状态机/后台 worker/sha256/consent/原子替换) 已符合该精神;换 self_update 反而放弃已有代理兜底与 7z 通道 - px() 几何常量:对话框/面板宽度与色点尺寸属文档化几何豁免, 机械换算会引入视觉回归;min 约束的 px(0.) 为布局断言 42 测试全绿,实机冒烟通过。
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
# MQTTX en 文案(rust-i18n:文件名即 locale,扁平 key: value)。
|
||||
# key = 语义标识符 ns.name;en/zh-CN 两文件键集合必须一致——单测强制。
|
||||
"aliyun.access_key_id": "AccessKey ID"
|
||||
"aliyun.access_key_secret": "AccessKey Secret"
|
||||
"aliyun.auth_mode": "Auth mode"
|
||||
"aliyun.auth_token_mode": "Group token"
|
||||
"aliyun.auth_triple_mode": "Per-device secret"
|
||||
"aliyun.build_failed": "Failed to build connection: {e}"
|
||||
"aliyun.device_id": "Device ID"
|
||||
"aliyun.device_secret": "DeviceSecret (per-device)"
|
||||
"aliyun.device_secret_ph": "Triple: DeviceSecret"
|
||||
"aliyun.generate_connect": "Generate & connect"
|
||||
"aliyun.group": "Group (optional)"
|
||||
"aliyun.group_id": "Group ID"
|
||||
"aliyun.help": "Token mode (Alibaba Cloud MQTT): endpoint {实例ID}.mqtt.aliyuncs.com, clientId={GroupId}@@@{设备ID}, password is the HMAC-SHA1 signature. Per-device secret (IoT Platform): endpoint {ProductKey}.iot-as-mqtt.{地域}.aliyuncs.com, signed per the official triple spec."
|
||||
"aliyun.instance_id": "Instance ID"
|
||||
"aliyun.preset_name": "Preset name"
|
||||
"aliyun.product_key": "ProductKey (per-device)"
|
||||
"aliyun.product_key_ph": "Triple: ProductKey"
|
||||
"aliyun.region": "Region"
|
||||
"aliyun.region_ph": "e.g. cn-shanghai (optional)"
|
||||
"aliyun.save_preset": "Save preset"
|
||||
"aliyun.saved_presets": "Saved presets"
|
||||
"aliyun.title": "Alibaba Cloud IoT"
|
||||
"color.blue": "Blue"
|
||||
"color.cyan": "Cyan"
|
||||
"color.green": "Green"
|
||||
"color.indigo": "Indigo"
|
||||
"color.magenta": "Magenta"
|
||||
"color.orange": "Orange"
|
||||
"color.purple": "Purple"
|
||||
"color.red": "Red"
|
||||
"color.rose": "Rose"
|
||||
"color.yellow": "Yellow"
|
||||
"common.advanced": "Advanced"
|
||||
"common.all": "All"
|
||||
"common.auto": "Auto"
|
||||
"common.browse": "Browse…"
|
||||
"common.cancel": "Cancel"
|
||||
"common.clear": "Clear"
|
||||
"common.close": "Close"
|
||||
"common.collapse": "Collapse"
|
||||
"common.delete": "Delete"
|
||||
"common.edit": "Edit"
|
||||
"common.new": "New"
|
||||
"common.pause": "Pause"
|
||||
"common.resume": "Resume"
|
||||
"common.retry": "Retry"
|
||||
"common.save": "Save"
|
||||
"common.send": "Send"
|
||||
"common.text": "Text"
|
||||
"common.update": "Update"
|
||||
"connview.logs": "Logs"
|
||||
"connview.messages": "Messages"
|
||||
"connview.subscriptions": "Subscriptions"
|
||||
"dir.published": "Published"
|
||||
"dir.received": "Received"
|
||||
"encode.base64_failed": "Base64 decode failed: {e}"
|
||||
"encode.hex_failed": "Hex decode failed: {e}"
|
||||
"encode.hex_invalid": "Hex decode failed: non-hex character present"
|
||||
"encode.hex_odd": "Hex length must be even (two chars per byte)"
|
||||
"form.auth_section": "Auth & Keep-alive"
|
||||
"form.auto_connect": "Connect on app start"
|
||||
"form.auto_reconnect": "Auto reconnect on disconnect"
|
||||
"form.auto_resub": "Auto-resubscribe on connect"
|
||||
"form.basics": "Basics"
|
||||
"form.cert_pair_required": "Client certificate and key must be provided together"
|
||||
"form.clean_start": "Clean Start / Clean Session"
|
||||
"form.client_id": "Client ID"
|
||||
"form.client_id_required": "Client ID cannot be empty"
|
||||
"form.conn_timeout": "Connect timeout (s)"
|
||||
"form.conn_timeout_invalid": "Connect timeout must be an integer 1-65535 (seconds)"
|
||||
"form.content_type_ph": "Content type (optional)"
|
||||
"form.err_positive": "must be > 0 (0 is an MQTT 5 protocol error)"
|
||||
"form.err_range": "must be an integer 0-65535"
|
||||
"form.err_uint": "must be a non-negative integer"
|
||||
"form.group": "Group"
|
||||
"form.host": "Host"
|
||||
"form.host_required": "Host cannot be empty"
|
||||
"form.keep_alive": "Keep Alive (s)"
|
||||
"form.keep_alive_short": "Keep Alive"
|
||||
"form.max_packet": "Maximum Packet Size"
|
||||
"form.max_reconnects": "Max reconnects (0 = unlimited)"
|
||||
"form.max_reconnects_invalid": "Max reconnects must be a non-negative integer (0 = unlimited)"
|
||||
"form.max_reconnects_ph": "0 = unlimited"
|
||||
"form.name": "Name"
|
||||
"form.name_ph": "Connection name"
|
||||
"form.name_required": "Connection name cannot be empty"
|
||||
"form.new_group": "New group"
|
||||
"form.new_group_hint": "Overrides the selected group when filled"
|
||||
"form.no_group": "No group"
|
||||
"form.password": "Password"
|
||||
"form.password_ph": "Password (optional)"
|
||||
"form.path_missing": "{label}: path not found: {p}"
|
||||
"form.port": "Port"
|
||||
"form.port_invalid": "Port must be an integer 1-65535"
|
||||
"form.protocol": "Protocol"
|
||||
"form.receive_max": "Receive Maximum"
|
||||
"form.receive_max_invalid": "Receive Maximum cannot exceed 65535"
|
||||
"form.receive_max_ph": "Server receive maximum"
|
||||
"form.receive_max_short": "Receive Maximum"
|
||||
"form.regen_client_id": "Regenerate Client ID"
|
||||
"form.response_topic_ph": "Response topic (optional)"
|
||||
"form.saved_reconnect": "Saved. Takes effect after reconnect"
|
||||
"form.session_expiry": "Session expiry (s)"
|
||||
"form.session_expiry_invalid": "Session expiry must be a non-negative integer (0 = session ends with connection)"
|
||||
"form.session_expiry_ph": "seconds (0 = session ends with connection)"
|
||||
"form.test": "Test Connection"
|
||||
"form.test_ok": "Connection test succeeded"
|
||||
"form.testing": "Testing…"
|
||||
"form.timeout_ph": "seconds (default 10)"
|
||||
"form.title": "Connection Settings"
|
||||
"form.topic_alias": "Topic Alias"
|
||||
"form.topic_alias_invalid": "Topic Alias Maximum cannot exceed 65535"
|
||||
"form.topic_alias_max": "Topic Alias Maximum"
|
||||
"form.transport": "Transport"
|
||||
"form.username": "Username"
|
||||
"form.username_ph": "Username (optional)"
|
||||
"form.will_content_type": "Content Type"
|
||||
"form.will_enable": "Enable last will"
|
||||
"form.will_payload": "Will payload"
|
||||
"form.will_payload_ph": "Will message"
|
||||
"form.will_response_topic": "Response Topic"
|
||||
"form.will_section": "Last Will"
|
||||
"form.will_topic": "Will topic"
|
||||
"form.ws_path": "WS Path"
|
||||
"form.ws_path_required": "WS path is required for WS/WSS transport"
|
||||
"io.err_item_id": "Item {n}: id must not be empty"
|
||||
"io.err_item_invalid": "Item {n} is not a valid connection: {e}"
|
||||
"io.err_missing_field": "JSON object lacks the \"connections\" array"
|
||||
"io.err_parse_json": "Failed to parse JSON: {e}"
|
||||
"io.err_parse_json_path": "Failed to parse {p}"
|
||||
"io.err_read": "Failed to read {p}: {e}"
|
||||
"io.err_serialize": "Failed to serialize connections: {e}"
|
||||
"io.err_utf8": "{p} is not valid UTF-8"
|
||||
"io.err_write": "Failed to write {p}: {e}"
|
||||
"io.exported": "Exported {n} connections to {path}"
|
||||
"io.imported": "Imported {n}, skipped {n2}"
|
||||
"kv.add": "Add property"
|
||||
"msg.binary_hint": " (binary — switch to Hex in details)"
|
||||
"msg.clear_confirm": "Clear all messages of this connection? This cannot be undone."
|
||||
"msg.clear_sub_filter": "Clear subscription filter"
|
||||
"msg.clear_title": "Clear Messages"
|
||||
"msg.clear_tooltip": "Clear messages of this connection"
|
||||
"msg.copy_details": "Copy full details (incl. MQTT 5 props)"
|
||||
"msg.copy_payload": "Copy raw payload"
|
||||
"msg.copy_topic": "Copy topic"
|
||||
"msg.count": "{n} msgs"
|
||||
"msg.details": "Details"
|
||||
"msg.details_copied": "Details copied"
|
||||
"msg.empty": "No messages yet — they will appear here once you subscribe"
|
||||
"msg.expiry": "Message Expiry"
|
||||
"msg.filter_placeholder": "Filter by topic or content…"
|
||||
"msg.latest_only": "Showing latest {n} only"
|
||||
"msg.log_title": "Connection Logs"
|
||||
"msg.no_logs": "No logs yet"
|
||||
"msg.no_match": "No matching messages"
|
||||
"msg.pause_tooltip": "Pause message stream (messages keep arriving in background)"
|
||||
"msg.payload": "Payload"
|
||||
"msg.payload_copied": "Payload copied"
|
||||
"msg.resume_tooltip": "Resume live scrolling"
|
||||
"msg.seconds": "{n}s"
|
||||
"msg.topic": "Topic"
|
||||
"msg.topic_copied": "Topic copied"
|
||||
"msg.truncated": "Truncated {n}KB"
|
||||
"preset.default_name": "New preset"
|
||||
"preset.default_name_n": "New preset {n}"
|
||||
"preset.empty": "No presets yet — click \"New\" above to create one"
|
||||
"preset.manage_title": "Manage Publish Presets"
|
||||
"preset.name_exists": "Preset name \"{name}\" already exists"
|
||||
"preset.payload_field": "Payload (stored raw; variables rendered on send)"
|
||||
"preset.payload_format": "Payload format"
|
||||
"preset.pick_delete_first": "Select a preset to delete first"
|
||||
"preset.pick_first": "Select or create a preset first"
|
||||
"preset.saved": "Preset \"{name}\" saved"
|
||||
"preset.section": "Publish Presets"
|
||||
"preset.user_props": "User Properties"
|
||||
"pub.collapse": "Collapse publish panel"
|
||||
"pub.collapsed_hint": "Publish panel collapsed"
|
||||
"pub.content_type_ph": "Content-Type (optional)"
|
||||
"pub.correlation_ph": "Correlation Data (optional)"
|
||||
"pub.delete_confirm": "Delete preset \"{name}\"? This cannot be undone."
|
||||
"pub.delete_item": "Delete \"{name}\""
|
||||
"pub.delete_title": "Delete Preset"
|
||||
"pub.expand": "Expand publish panel"
|
||||
"pub.expiry_field": "Expiry (s)"
|
||||
"pub.expiry_invalid": "Message expiry must be a non-negative integer (seconds)"
|
||||
"pub.json_invalid": "Invalid JSON: {e}"
|
||||
"pub.manage_presets": "Manage presets…"
|
||||
"pub.mqtt5_props": "MQTT 5 Properties"
|
||||
"pub.offline_not_sent": "Offline: message not sent"
|
||||
"pub.payload_placeholder": "Message payload; supports {{variables}} and {{$ts}} {{$uuid}}"
|
||||
"pub.payload_preset_ph": "Message payload; supports {{variables}}"
|
||||
"pub.preset_menu": "Message Presets"
|
||||
"pub.preset_name_hint": "Preset name, e.g. temperature-report"
|
||||
"pub.preset_name_required": "Preset name is required"
|
||||
"pub.preset_overwritten": "Preset \"{name}\" overwritten"
|
||||
"pub.preset_saved": "Preset saved"
|
||||
"pub.response_topic_ph": "Response Topic (optional)"
|
||||
"pub.save_preset": "Save current as preset…"
|
||||
"pub.save_title": "Save Message Preset"
|
||||
"pub.seconds_hint": "seconds, e.g. 60"
|
||||
"pub.send_disabled": "Offline — cannot send"
|
||||
"pub.sent_hidden": "Sent (hidden by current filter — not shown in the stream)"
|
||||
"pub.topic_placeholder": "Publish topic"
|
||||
"pub.topic_preset_ph": "Publish topic; supports {{variables}}"
|
||||
"pub.topic_required": "Publish topic cannot be empty"
|
||||
"pub.vars": "Variable injection"
|
||||
"pub.vars_count": "Variable injection ({n} placeholders)"
|
||||
"resmon.auto_refresh": "Auto refresh"
|
||||
"resmon.budget": "Budget {p}% used ({used} / {total})"
|
||||
"resmon.committed": "Committed memory"
|
||||
"resmon.committed_hint": "Physical pages committed by the process"
|
||||
"resmon.cpu": "CPU usage"
|
||||
"resmon.cpu_hint": "Normalized by core count"
|
||||
"resmon.n_msgs": "{n} msgs"
|
||||
"resmon.no_conn": "No connections"
|
||||
"resmon.peak": "Peak {v}"
|
||||
"resmon.refresh_now": "Refresh now"
|
||||
"resmon.section": "Messages & Connections"
|
||||
"resmon.stats": "{n} connections ({m} connected) · {t} tabs · {msgs} messages in memory (cap {cap}/conn) · {logs} log lines"
|
||||
"resmon.threads_handles": "Threads · handles"
|
||||
"resmon.threads_hint": "tokio engine + GPUI renderer"
|
||||
"resmon.uptime": "Up {t}"
|
||||
"resmon.version_live": "Version v{v} · refreshes every 1.5s"
|
||||
"resmon.version_paused": "Version v{v} · auto-refresh paused"
|
||||
"resmon.working_set": "Working set"
|
||||
"settings.about": "About"
|
||||
"settings.auto_update": "Check for updates on start"
|
||||
"settings.byte_budget": "Plus a per-connection byte budget of {n} MB: under heavy traffic, oldest messages are evicted by size first"
|
||||
"settings.data_dir": "Data directory"
|
||||
"settings.language": "Language"
|
||||
"settings.license": "Apache-2.0 license"
|
||||
"settings.log_dir": "Log directory"
|
||||
"settings.max_messages": "Messages kept in memory per connection (100-100000)"
|
||||
"settings.mkdir_failed": "Failed to create log folder: {e}"
|
||||
"settings.open_dir": "Open folder"
|
||||
"settings.open_dir_failed": "Failed to open folder: {e}"
|
||||
"settings.open_log_dir": "Open log folder"
|
||||
"settings.show_millis": "Show milliseconds in timestamps"
|
||||
"settings.tagline": "A cross-platform MQTT debug client built with GPUI, aiming at MQTTX"
|
||||
"settings.theme": "Theme"
|
||||
"settings.version_line": "Version {v} (update source: {src})"
|
||||
"sidebar.collapse": "Collapse sidebar (Ctrl+B)"
|
||||
"sidebar.connections": "Connections"
|
||||
"sidebar.delete": "Delete Connection"
|
||||
"sidebar.delete_confirm": "Delete \"{name}\"? This also deletes {sub_count} subscriptions and disconnects. This cannot be undone."
|
||||
"sidebar.disconnect": "Disconnect"
|
||||
"sidebar.dup_suffix": "{name} (copy)"
|
||||
"sidebar.duplicate": "Duplicate"
|
||||
"sidebar.empty": "No connections yet. Click + above to create one"
|
||||
"sidebar.expand": "Expand sidebar (Ctrl+B)"
|
||||
"sidebar.no_match": "No matching connections"
|
||||
"sidebar.search": "Search connections…"
|
||||
"sidebar.ungrouped": "Ungrouped"
|
||||
"ssl.ca": "CA certificate (PEM)"
|
||||
"ssl.ca_ph": "Path to CA certificate (PEM, optional)"
|
||||
"ssl.cert": "Client certificate (PEM)"
|
||||
"ssl.cert_ph": "Path to client certificate (PEM, optional)"
|
||||
"ssl.key": "Client key (PEM)"
|
||||
"ssl.key_ph": "Path to client private key (PEM, optional)"
|
||||
"ssl.n_configured": "{n} configured"
|
||||
"ssl.pick_file": "Select certificate / key file"
|
||||
"ssl.section": "SSL/TLS"
|
||||
"ssl.skip_verify": "Skip CA verification (trust any server cert)"
|
||||
"status.connected": "Connected"
|
||||
"status.connecting": "Connecting"
|
||||
"status.disconnected": "Disconnected"
|
||||
"status.error": "Error"
|
||||
"sub.alias": "Alias (optional)"
|
||||
"sub.cancel_edit": "Cancel editing"
|
||||
"sub.collapse": "Collapse subscriptions panel"
|
||||
"sub.color": "Subscription color"
|
||||
"sub.color_clear": "Clear color"
|
||||
"sub.color_random": "Random color"
|
||||
"sub.disable": "Disable subscription"
|
||||
"sub.edit": "Edit subscription"
|
||||
"sub.editing": "Editing: {topic}"
|
||||
"sub.empty_hint": "Subscribe to a topic and messages will show up here"
|
||||
"sub.enable": "Enable subscription"
|
||||
"sub.expand": "Expand subscriptions panel"
|
||||
"sub.failed": "Subscribe failed: {e}"
|
||||
"sub.identifier": "Subscription Identifier"
|
||||
"sub.identifier_field": "Subscription Identifier"
|
||||
"sub.identifier_invalid": "Subscription identifier must be a positive integer"
|
||||
"sub.mqtt5_options": "MQTT 5 Subscription Options"
|
||||
"sub.no_local": "No Local"
|
||||
"sub.rap": "Retain As Published"
|
||||
"sub.retain_handling": "Retain Handling"
|
||||
"sub.rh0": "0 Send at every subscribe"
|
||||
"sub.rh1": "1 Send only for new subscription"
|
||||
"sub.rh2": "2 Do not send"
|
||||
"sub.saved_offline": "Offline: subscription saved"
|
||||
"sub.saved_offline_resub": "Offline: subscription saved, restored on connect"
|
||||
"sub.topic_placeholder": "Topic filter, supports # +; separate multiple with commas/spaces"
|
||||
"sub.topic_required": "Topic filter cannot be empty"
|
||||
"sub.unsubscribe": "Unsubscribe"
|
||||
"tab.welcome_hint": "Pick a connection on the left, or create a new one to start debugging"
|
||||
"tab.welcome_title": "Get started with MQTTX"
|
||||
"theme.dark": "Dark"
|
||||
"theme.light": "Light"
|
||||
"theme.system": "Follow system"
|
||||
"titlebar.aliyun": "Alibaba Cloud Devices"
|
||||
"titlebar.export": "Export Connections"
|
||||
"titlebar.import": "Import Connections"
|
||||
"titlebar.new_connection": "New Connection"
|
||||
"titlebar.pin": "Pin window on top"
|
||||
"titlebar.pin_failed": "Failed to set always-on-top"
|
||||
"titlebar.resmon": "Resource Monitor"
|
||||
"titlebar.settings": "Settings"
|
||||
"titlebar.subtitle": "MQTT debug client"
|
||||
"titlebar.switch_dark": "Switch to dark theme"
|
||||
"titlebar.switch_light": "Switch to light theme"
|
||||
"titlebar.unpin": "Unpin window"
|
||||
"titlebar.variables": "Global Variables"
|
||||
"update.apply": "Update to v{v} now"
|
||||
"update.check": "Check for updates"
|
||||
"update.check_hint": "Check GitHub Releases for a new version"
|
||||
"update.checking": "Checking…"
|
||||
"update.checking_long": "Checking for updates…"
|
||||
"update.discard": "Discard this update"
|
||||
"update.discarded": "Staged update discarded"
|
||||
"update.downloading": "Downloading {pct}% ({done} / {total})"
|
||||
"update.downloading_size": "Downloading {done}"
|
||||
"update.failed": "Update failed: {e}"
|
||||
"update.found": "New version v{v} found — downloads, replaces itself and restarts"
|
||||
"update.install_failed": "Install failed: {e}"
|
||||
"update.install_later": "Install on next start"
|
||||
"update.install_now": "Install now"
|
||||
"update.not_now": "Not now"
|
||||
"update.ready": "Version v{v} downloaded and verified, ready to install"
|
||||
"update.ready_body": "Version v{v} has been downloaded (sha256 verified)."
|
||||
"update.ready_title": "Update Ready"
|
||||
"update.recheck": "Check again"
|
||||
"update.staged": "Staged — it will install on next start"
|
||||
"update.up_to_date": "You are up to date"
|
||||
"var.builtin_inline": "Built-in: {{$ts}} (s), {{$ts_ms}} (ms), {{$uuid}} · injected on publish"
|
||||
"var.dialog_builtin": "Built-in: {{$ts}} (s), {{$ts_ms}} (ms), {{$uuid}}"
|
||||
"var.dialog_hint": "Reference {{variables}} in publish topics and payloads."
|
||||
"var.no_reference": "No {{variable}} reference in topic/payload"
|
||||
"var.preview": "Preview: {tp} | {pl}"
|
||||
"var.reextract": "Re-extract placeholders"
|
||||
"var.section": "Variables ({n})"
|
||||
"var.value": "Value"
|
||||
"will.enabled_hint": "Enabled"
|
||||
"will.topic_required": "Will topic cannot be empty"
|
||||
"will.topic_wildcard": "Will topic cannot contain wildcards + or #"
|
||||
Reference in New Issue
Block a user