fix(phone-connect): zh-Hans locale, aliasMap, box padding, volume, config data loss, interval, dead settings, slider, UI issues (#199)
* fix(phone-connect): zh-Hans locale, switcher aliasMap, box padding - Rename zh_hans -> zh-Hans (plugin.toml value, en.json key) and add the missing translations/zh-Hans.json. The zh_hans tag does not match BCP 47, so the framework's materialize step dropped the Chinese translation file and t() returned raw dot keys in the UI. - Add missing aliasMap definition in switcher() so the multi-device panel no longer errors on aliasMap[id]. Fixes #176. - Replace ui.box({padding=...}) with ui.row({padding=...}); box does not support the padding prop, causing ui-tree warnings on open. Supersedes #190 (which covers only the aliasMap fix). * fix(phone-connect): volume quoting, config data loss, interval, dead settings, slider, UI issues 1. media_set_volume: shellQuote the <int32> variant arg (was unquoted, shell treated < as redirect; same fix as media_seek). 2. onConfigChanged: track last-applied custom_image/device_alias values so unrelated config edits (e.g. language switch) no longer wipe a device's image/alias. Seed trackers at init so reload is safe too. 3. state_update_interval=0: return 3600000ms + guard in update() to skip auto-refresh, matching the '0 disables' description. 4. Remove 4 dead settings (show_ongoing_media, show_device_placeholder, scan_subdirectories, max_recent_images) declared but never read, plus orphan recent_images translation keys. 5. Media slider: freeze value during drag via per-device dragState so the 1s service refresh doesn't yank the slider back. Also fixes onDragEnd seeking to a stale seekPos. 6. Minor: runCli reports cli_failed not no_backend; browse uses runAsync not runInTerminal; charging color priority over low-battery; device name falls back to d.name; fix comment mismatches. * chore: bump version to 0.1.1 * fix: use lowercase zh-hans translation key to pass CI validation CI rejects uppercase in translation keys (zh-Hans). The translation key and label_key are now lowercase zh-hans, while the file name stays zh-Hans.json (required by the framework's materialize step) and the select value stays zh-Hans (used to build the file path).
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
-- panel.luau
|
-- panel.luau
|
||||||
-- Phone Connect - details panel (layered info-style UI, 720x600).
|
-- Phone Connect - details panel (layered info-style UI, 500x600).
|
||||||
--
|
--
|
||||||
-- Layout: header + device hero + info grid span the top (full width); below
|
-- Layout: header + device hero + info grid span the top (full width); below
|
||||||
-- them a two-column row: left (flex, scrollable) holds media control + SMS
|
-- them a two-column row: left (flex, scrollable) holds media control + SMS
|
||||||
@@ -27,6 +27,12 @@ end
|
|||||||
local smsDestination = ""
|
local smsDestination = ""
|
||||||
local smsMessage = ""
|
local smsMessage = ""
|
||||||
|
|
||||||
|
-- ── Media slider drag state (freeze value while dragging) ──────────────────
|
||||||
|
-- slider is controlled: value is reset every render. While the user drags,
|
||||||
|
-- we freeze value to their position so the 1s service refresh doesn't yank
|
||||||
|
-- the slider back. Keyed by device id so multi-device switching is clean.
|
||||||
|
local dragState = {} -- [deviceId] = { dragging = true, position = <ms> }
|
||||||
|
|
||||||
-- ── Helpers ─────────────────────────────────────────────────────────────────
|
-- ── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
local function glyphForType(t)
|
local function glyphForType(t)
|
||||||
if t == "tablet" then return "device-tablet" end
|
if t == "tablet" then return "device-tablet" end
|
||||||
@@ -61,7 +67,7 @@ local function heroCard(d)
|
|||||||
local reachable = d.isReachable == true
|
local reachable = d.isReachable == true
|
||||||
local paired = d.isPaired == true
|
local paired = d.isPaired == true
|
||||||
local aliasMap = noctalia.state.get("pc.aliasMap") or {}
|
local aliasMap = noctalia.state.get("pc.aliasMap") or {}
|
||||||
local name = aliasMap[d.id] or "Your-Phone"
|
local name = aliasMap[d.id] or d.name or "Your-Phone"
|
||||||
local glyph = glyphForType(d.type)
|
local glyph = glyphForType(d.type)
|
||||||
local imgMap = noctalia.state.get("pc.imageMap") or {}
|
local imgMap = noctalia.state.get("pc.imageMap") or {}
|
||||||
local imgPath = imgMap[d.id]
|
local imgPath = imgMap[d.id]
|
||||||
@@ -92,7 +98,7 @@ local function heroCard(d)
|
|||||||
}),
|
}),
|
||||||
ui.column({ gap = 4, flexGrow = 1 }, {
|
ui.column({ gap = 4, flexGrow = 1 }, {
|
||||||
ui.label({ text = name, fontSize = 17, fontWeight = "bold", color = "on_surface" }),
|
ui.label({ text = name, fontSize = 17, fontWeight = "bold", color = "on_surface" }),
|
||||||
ui.box({ radius = 10, fill = statusColor .. "/0.15", padding = 4 }, {
|
ui.row({ radius = 10, fill = statusColor .. "/0.15", padding = 4 }, {
|
||||||
ui.label({ text = statusText, fontSize = 11, color = statusColor }),
|
ui.label({ text = statusText, fontSize = 11, color = statusColor }),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -228,18 +234,22 @@ local function mediaSection(d)
|
|||||||
|
|
||||||
local progress
|
local progress
|
||||||
if canSeek and length > 0 then
|
if canSeek and length > 0 then
|
||||||
local seekPos = position
|
local st = dragState[d.id]
|
||||||
|
local sliderValue = (st and st.dragging and st.position) or position
|
||||||
progress = ui.column({ gap = 2 }, {
|
progress = ui.column({ gap = 2 }, {
|
||||||
ui.slider({ min = 0, max = length, step = 1000, value = position,
|
ui.slider({ min = 0, max = length, step = 1000, value = sliderValue,
|
||||||
controlSize = "sm",
|
controlSize = "sm",
|
||||||
onChange = function(v)
|
onChange = function(v)
|
||||||
seekPos = tonumber(v) or position
|
dragState[d.id] = { dragging = true, position = tonumber(v) or position }
|
||||||
end,
|
end,
|
||||||
onDragEnd = function()
|
onDragEnd = function()
|
||||||
sendCmd({ op = "media_seek", device = d.id, offset = tostring(seekPos) })
|
local st2 = dragState[d.id]
|
||||||
|
local target = (st2 and st2.position) or position
|
||||||
|
dragState[d.id] = nil
|
||||||
|
sendCmd({ op = "media_seek", device = d.id, offset = tostring(target) })
|
||||||
end }),
|
end }),
|
||||||
ui.row({ justify = "space_between" }, {
|
ui.row({ justify = "space_between" }, {
|
||||||
ui.label({ text = fmtTime(position), fontSize = 9, color = "on_surface_variant" }),
|
ui.label({ text = fmtTime(sliderValue), fontSize = 9, color = "on_surface_variant" }),
|
||||||
ui.label({ text = fmtTime(length), fontSize = 9, color = "on_surface_variant" }),
|
ui.label({ text = fmtTime(length), fontSize = 9, color = "on_surface_variant" }),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -295,6 +305,7 @@ end
|
|||||||
|
|
||||||
-- ── Device switcher (bottom, full width, only if >1 device) ─────────────────
|
-- ── Device switcher (bottom, full width, only if >1 device) ─────────────────
|
||||||
local function switcher(devices, order, sel)
|
local function switcher(devices, order, sel)
|
||||||
|
local aliasMap = noctalia.state.get("pc.aliasMap") or {}
|
||||||
if #order <= 1 then return nil end
|
if #order <= 1 then return nil end
|
||||||
local items = {}
|
local items = {}
|
||||||
for _, id in ipairs(order) do
|
for _, id in ipairs(order) do
|
||||||
@@ -308,7 +319,7 @@ local function switcher(devices, order, sel)
|
|||||||
}, {
|
}, {
|
||||||
ui.glyph({ name = glyphForType(d.type), size = 14,
|
ui.glyph({ name = glyphForType(d.type), size = 14,
|
||||||
color = d.isReachable and "primary" or "on_surface_variant" }),
|
color = d.isReachable and "primary" or "on_surface_variant" }),
|
||||||
ui.label({ text = aliasMap[id] or "Your-Phone", fontSize = 12, flexGrow = 1, color = "on_surface" }),
|
ui.label({ text = aliasMap[id] or d.name or "Your-Phone", fontSize = 12, flexGrow = 1, color = "on_surface" }),
|
||||||
ui.label({ text = (type(d.batteryCharge) == "number" and d.batteryCharge >= 0)
|
ui.label({ text = (type(d.batteryCharge) == "number" and d.batteryCharge >= 0)
|
||||||
and (tostring(d.batteryCharge) .. "%") or "", fontSize = 10,
|
and (tostring(d.batteryCharge) .. "%") or "", fontSize = 10,
|
||||||
color = "on_surface_variant" }),
|
color = "on_surface_variant" }),
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
id = "icefish/phone-connect"
|
id = "icefish/phone-connect"
|
||||||
name = "Phone Connect"
|
name = "Phone Connect"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
plugin_api = 16
|
plugin_api = 16
|
||||||
author = "icefish"
|
author = "icefish"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -66,7 +66,7 @@ description_key = "settings.language.description"
|
|||||||
default = "en"
|
default = "en"
|
||||||
options = [
|
options = [
|
||||||
{ value = "en", label_key = "settings.language.en" },
|
{ value = "en", label_key = "settings.language.en" },
|
||||||
{ value = "zh_hans", label_key = "settings.language.zh_hans" },
|
{ value = "zh-Hans", label_key = "settings.language.zh-hans" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[setting]]
|
[[setting]]
|
||||||
@@ -76,36 +76,6 @@ label_key = "settings.enable_clipboard_action.label"
|
|||||||
description_key = "settings.enable_clipboard_action.description"
|
description_key = "settings.enable_clipboard_action.description"
|
||||||
default = true
|
default = true
|
||||||
|
|
||||||
[[setting]]
|
|
||||||
key = "show_ongoing_media"
|
|
||||||
type = "bool"
|
|
||||||
label_key = "settings.show_ongoing_media.label"
|
|
||||||
description_key = "settings.show_ongoing_media.description"
|
|
||||||
default = true
|
|
||||||
|
|
||||||
[[setting]]
|
|
||||||
key = "show_device_placeholder"
|
|
||||||
type = "bool"
|
|
||||||
label_key = "settings.show_device_placeholder.label"
|
|
||||||
description_key = "settings.show_device_placeholder.description"
|
|
||||||
default = true
|
|
||||||
|
|
||||||
[[setting]]
|
|
||||||
key = "scan_subdirectories"
|
|
||||||
type = "bool"
|
|
||||||
label_key = "settings.scan_subdirectories.label"
|
|
||||||
description_key = "settings.scan_subdirectories.description"
|
|
||||||
default = false
|
|
||||||
|
|
||||||
[[setting]]
|
|
||||||
key = "max_recent_images"
|
|
||||||
type = "int"
|
|
||||||
label_key = "settings.max_recent_images.label"
|
|
||||||
description_key = "settings.max_recent_images.description"
|
|
||||||
default = 4
|
|
||||||
min = 1
|
|
||||||
max = 12
|
|
||||||
|
|
||||||
[[setting]]
|
[[setting]]
|
||||||
key = "panel_placement"
|
key = "panel_placement"
|
||||||
type = "select"
|
type = "select"
|
||||||
@@ -130,10 +100,10 @@ entry = "service.luau"
|
|||||||
id = "bar"
|
id = "bar"
|
||||||
entry = "widget.luau"
|
entry = "widget.luau"
|
||||||
|
|
||||||
# Right-click opens the details panel via onRightClick in the script
|
# Right-click opens settings via onRightClick in the script
|
||||||
# (noctalia.togglePanel). We deliberately do not bind [widget.actions] here:
|
# (noctalia.openSettings). Left-click toggles the details panel. We deliberately
|
||||||
# the panel-toggle action grammar for opening a *plugin* panel by id is not
|
# do not bind [widget.actions] here: the panel-toggle action grammar for opening
|
||||||
# documented, so we drive it from the script instead.
|
# a *plugin* panel by id is not documented, so we drive it from the script instead.
|
||||||
|
|
||||||
[[shortcut]]
|
[[shortcut]]
|
||||||
id = "tile"
|
id = "tile"
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ local function t(key)
|
|||||||
return table[key] or key
|
return table[key] or key
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Track last-applied config values so onConfigChanged only re-applies
|
||||||
|
-- custom_image / device_alias when they actually change, not on every
|
||||||
|
-- unrelated config edit (e.g. language switch). Prevents wiping a
|
||||||
|
-- device's image/alias that was set via the global setting.
|
||||||
|
local lastCustomImage = nil
|
||||||
|
local lastDeviceAlias = nil
|
||||||
|
|
||||||
-- ── State contract (read by UI entries) ──────────────────────────────────────
|
-- ── State contract (read by UI entries) ──────────────────────────────────────
|
||||||
-- noctalia.state "pc.backend" : { available=bool, name="KDE Connect"|"None",
|
-- noctalia.state "pc.backend" : { available=bool, name="KDE Connect"|"None",
|
||||||
-- announcedName="", selfId="" }
|
-- announcedName="", selfId="" }
|
||||||
@@ -484,7 +491,7 @@ end
|
|||||||
local function runCli(args, cb)
|
local function runCli(args, cb)
|
||||||
noctalia.runAsync("kdeconnect-cli " .. args, function(res)
|
noctalia.runAsync("kdeconnect-cli " .. args, function(res)
|
||||||
if res and res.error then
|
if res and res.error then
|
||||||
noctalia.notifyError(t("error.no_backend"), res.error)
|
noctalia.notifyError(t("error.cli_failed"), res.error)
|
||||||
end
|
end
|
||||||
if cb then cb(res) end
|
if cb then cb(res) end
|
||||||
end)
|
end)
|
||||||
@@ -596,7 +603,7 @@ local function handleCommand(cmd)
|
|||||||
local first = (res.stdout or ""):match("'([^']+)'")
|
local first = (res.stdout or ""):match("'([^']+)'")
|
||||||
if first then dirPath = first end
|
if first then dirPath = first end
|
||||||
end
|
end
|
||||||
noctalia.runInTerminal("xdg-open " .. shellQuote(dirPath))
|
noctalia.runAsync("xdg-open " .. shellQuote(dirPath), function() end)
|
||||||
noctalia.notify(t("notify.opening_browser"), dirPath)
|
noctalia.notify(t("notify.opening_browser"), dirPath)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
@@ -664,7 +671,7 @@ local function handleCommand(cmd)
|
|||||||
.. " --object-path " .. shellQuote(path)
|
.. " --object-path " .. shellQuote(path)
|
||||||
.. " --method " .. shellQuote(PROPS_IFACE .. ".Set")
|
.. " --method " .. shellQuote(PROPS_IFACE .. ".Set")
|
||||||
.. " " .. shellQuote(MPRIS_IFACE) .. " volume "
|
.. " " .. shellQuote(MPRIS_IFACE) .. " volume "
|
||||||
.. "<int32 " .. tostring(vol) .. ">",
|
.. shellQuote("<int32 " .. tostring(vol) .. ">"),
|
||||||
function() refreshDevices() end)
|
function() refreshDevices() end)
|
||||||
else
|
else
|
||||||
noctalia.log("[phone-connect] unknown cmd op: " .. tostring(op))
|
noctalia.log("[phone-connect] unknown cmd op: " .. tostring(op))
|
||||||
@@ -682,7 +689,7 @@ end
|
|||||||
-- ── Lifecycle ────────────────────────────────────────────────────────────────
|
-- ── Lifecycle ────────────────────────────────────────────────────────────────
|
||||||
local function intervalMs()
|
local function intervalMs()
|
||||||
local secs = tonumber(noctalia.getConfig("state_update_interval")) or 30
|
local secs = tonumber(noctalia.getConfig("state_update_interval")) or 30
|
||||||
if secs <= 0 then return 60000 end
|
if secs <= 0 then return 3600000 end -- disabled: large interval to minimize wakeups
|
||||||
-- if media is playing, poll faster for live position updates
|
-- if media is playing, poll faster for live position updates
|
||||||
for _, d in pairs(devices) do
|
for _, d in pairs(devices) do
|
||||||
if d.mediaisPlaying == true then return 1000 end
|
if d.mediaisPlaying == true then return 1000 end
|
||||||
@@ -691,16 +698,19 @@ local function intervalMs()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function update()
|
function update()
|
||||||
|
local secs = tonumber(noctalia.getConfig("state_update_interval")) or 30
|
||||||
noctalia.setUpdateInterval(intervalMs())
|
noctalia.setUpdateInterval(intervalMs())
|
||||||
|
if secs <= 0 then return end -- disabled: skip auto-refresh
|
||||||
detectBackend()
|
detectBackend()
|
||||||
refreshDevices()
|
refreshDevices()
|
||||||
end
|
end
|
||||||
|
|
||||||
function onConfigChanged()
|
function onConfigChanged()
|
||||||
noctalia.setUpdateInterval(intervalMs())
|
noctalia.setUpdateInterval(intervalMs())
|
||||||
-- apply custom_image to selected device
|
-- apply custom_image to selected device (only when actually changed)
|
||||||
local img = noctalia.getConfig("custom_image")
|
local img = noctalia.getConfig("custom_image")
|
||||||
if img ~= nil then
|
if img ~= nil and img ~= lastCustomImage then
|
||||||
|
lastCustomImage = img
|
||||||
local sel = noctalia.state.get("pc.selected")
|
local sel = noctalia.state.get("pc.selected")
|
||||||
if sel and sel ~= "" then
|
if sel and sel ~= "" then
|
||||||
local map = noctalia.state.get("pc.imageMap") or {}
|
local map = noctalia.state.get("pc.imageMap") or {}
|
||||||
@@ -709,9 +719,10 @@ function onConfigChanged()
|
|||||||
persistImageMap(map)
|
persistImageMap(map)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- apply device_alias to selected device
|
-- apply device_alias to selected device (only when actually changed)
|
||||||
local alias = noctalia.getConfig("device_alias")
|
local alias = noctalia.getConfig("device_alias")
|
||||||
if alias ~= nil then
|
if alias ~= nil and alias ~= lastDeviceAlias then
|
||||||
|
lastDeviceAlias = alias
|
||||||
local sel = noctalia.state.get("pc.selected")
|
local sel = noctalia.state.get("pc.selected")
|
||||||
if sel and sel ~= "" then
|
if sel and sel ~= "" then
|
||||||
local amap = noctalia.state.get("pc.aliasMap") or {}
|
local amap = noctalia.state.get("pc.aliasMap") or {}
|
||||||
@@ -744,6 +755,10 @@ function onIpc(event, payload)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Top-level init runs once at load.
|
-- Top-level init runs once at load.
|
||||||
|
-- Seed config trackers so the first onConfigChanged after reload doesn't
|
||||||
|
-- re-apply default custom_image/device_alias and wipe a device's image/alias.
|
||||||
|
lastCustomImage = noctalia.getConfig("custom_image") or ""
|
||||||
|
lastDeviceAlias = noctalia.getConfig("device_alias") or ""
|
||||||
detectBackend()
|
detectBackend()
|
||||||
refreshDevices()
|
refreshDevices()
|
||||||
-- Command channel: UI entries write { op, device, seq, ... } to "pc.cmd".
|
-- Command channel: UI entries write { op, device, seq, ... } to "pc.cmd".
|
||||||
|
|||||||
@@ -1,118 +1,101 @@
|
|||||||
{
|
{
|
||||||
"action": {
|
"settings": {
|
||||||
"accept": "Accept",
|
"state_update_interval": {
|
||||||
"browse": "Browse",
|
"label": "State Update Interval",
|
||||||
"clipboard": "Clipboard",
|
"description": "Seconds between automatic device state refreshes. 0 disables auto-refresh."
|
||||||
"pair": "Pair",
|
|
||||||
"ping": "Ping",
|
|
||||||
"refresh": "Refresh",
|
|
||||||
"reject": "Reject",
|
|
||||||
"ring": "Ring",
|
|
||||||
"share": "Share",
|
|
||||||
"sms": "SMS",
|
|
||||||
"switch": "Switch device",
|
|
||||||
"unpair": "Unpair"
|
|
||||||
},
|
},
|
||||||
"error": {
|
"enable_charging_animation": {
|
||||||
"accept_failed": "Failed to accept pairing",
|
"label": "Show Charging Fill",
|
||||||
"browse_failed": "Failed to browse device",
|
"description": "Highlight the bar widget when the device is charging."
|
||||||
"clipboard_failed": "Failed to send clipboard",
|
},
|
||||||
"no_backend": "No backend",
|
"custom_image": {
|
||||||
"pairing_failed": "Pairing failed",
|
"label": "Device Image",
|
||||||
"ping_failed": "Failed to send ping",
|
"description": "Set a custom image for the selected device. Leave empty for default icon."
|
||||||
"reject_failed": "Failed to reject pairing",
|
},
|
||||||
"ring_failed": "Failed to ring device",
|
"device_alias": {
|
||||||
"share_failed": "Failed to share",
|
"label": "Device Alias",
|
||||||
"unpair_failed": "Unpair failed"
|
"description": "Custom display name for the selected device."
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"label": "Language",
|
||||||
|
"description": "Display language",
|
||||||
|
"en": "English",
|
||||||
|
"zh-hans": "简体中文"
|
||||||
|
},
|
||||||
|
"enable_clipboard_action": {
|
||||||
|
"label": "Show Clipboard Action",
|
||||||
|
"description": "Show a quick action to send the clipboard to the device."
|
||||||
|
},
|
||||||
|
"panel_placement": {
|
||||||
|
"label": "Panel Placement",
|
||||||
|
"description": "How the details panel opens: attached to the bar, or floating.",
|
||||||
|
"attached": "Attached to bar",
|
||||||
|
"floating": "Floating (center)",
|
||||||
|
"widget": "Below widget (floating)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"unavailable": "Unavailable",
|
||||||
|
"no_devices": "No devices",
|
||||||
|
"offline": "Offline",
|
||||||
|
"connected": "Connected",
|
||||||
|
"not_paired": "Not paired",
|
||||||
|
"no_backend": "KDE Connect not running"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"ring": "Ring",
|
||||||
|
"ping": "Ping",
|
||||||
|
"clipboard": "Clipboard",
|
||||||
|
"share": "Share",
|
||||||
|
"browse": "Browse",
|
||||||
|
"sms": "SMS",
|
||||||
|
"pair": "Pair",
|
||||||
|
"accept": "Accept",
|
||||||
|
"reject": "Reject",
|
||||||
|
"unpair": "Unpair",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"switch": "Switch device"
|
||||||
},
|
},
|
||||||
"notify": {
|
"notify": {
|
||||||
"clipboard_sent": "Clipboard sent",
|
|
||||||
"opening_browser": "Opening file browser...",
|
|
||||||
"paired": "Device paired",
|
|
||||||
"pairing_sent": "Pairing request sent",
|
|
||||||
"ping_sent": "Ping sent to {name}",
|
|
||||||
"ringing": "Ringing {name}...",
|
"ringing": "Ringing {name}...",
|
||||||
"sms_sent": "SMS sent",
|
"ping_sent": "Ping sent to {name}",
|
||||||
"unpaired": "Device unpaired"
|
"clipboard_sent": "Clipboard sent",
|
||||||
|
"pairing_sent": "Pairing request sent",
|
||||||
|
"paired": "Device paired",
|
||||||
|
"unpaired": "Device unpaired",
|
||||||
|
"opening_browser": "Opening file browser...",
|
||||||
|
"sms_sent": "SMS sent"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"ring_failed": "Failed to ring device",
|
||||||
|
"ping_failed": "Failed to send ping",
|
||||||
|
"clipboard_failed": "Failed to send clipboard",
|
||||||
|
"share_failed": "Failed to share",
|
||||||
|
"browse_failed": "Failed to browse device",
|
||||||
|
"pairing_failed": "Pairing failed",
|
||||||
|
"accept_failed": "Failed to accept pairing",
|
||||||
|
"reject_failed": "Failed to reject pairing",
|
||||||
|
"unpair_failed": "Unpair failed",
|
||||||
|
"no_backend": "No backend",
|
||||||
|
"cli_failed": "Command failed"
|
||||||
},
|
},
|
||||||
"panel": {
|
"panel": {
|
||||||
"battery": "Battery",
|
"title": "Phone Connect",
|
||||||
"devices_connected": "{connected} connected • {paired} paired",
|
|
||||||
"empty": "No devices found. Pair a device in KDE Connect.",
|
"empty": "No devices found. Pair a device in KDE Connect.",
|
||||||
"image_path": "Custom Image",
|
"devices_connected": "{connected} connected • {paired} paired",
|
||||||
"network": "Network",
|
|
||||||
"no_media": "No media playing",
|
|
||||||
"no_recent_images": "No recent images found",
|
|
||||||
"now_playing": "Now Playing",
|
"now_playing": "Now Playing",
|
||||||
"paired": "Paired",
|
"no_media": "No media playing",
|
||||||
"pairing": "Pairing",
|
"image_path": "Custom Image",
|
||||||
"recent_images": "Recent Images",
|
|
||||||
"sms": "Send SMS",
|
"sms": "Send SMS",
|
||||||
"sms_dest": "Phone number",
|
"sms_dest": "Phone number",
|
||||||
"sms_msg": "Message",
|
"sms_msg": "Message",
|
||||||
"sms_send": "Send",
|
"sms_send": "Send",
|
||||||
"title": "Phone Connect",
|
"battery": "Battery",
|
||||||
|
"network": "Network",
|
||||||
|
"pairing": "Pairing",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
"unknown": "unknown",
|
"paired": "Paired",
|
||||||
"unpaired": "Unpaired"
|
"unpaired": "Unpaired",
|
||||||
},
|
"unknown": "unknown"
|
||||||
"settings": {
|
|
||||||
"custom_image": {
|
|
||||||
"description": "Set a custom image for the selected device. Leave empty for default icon.",
|
|
||||||
"label": "Device Image"
|
|
||||||
},
|
|
||||||
"device_alias": {
|
|
||||||
"description": "Custom display name for the selected device.",
|
|
||||||
"label": "Device Alias"
|
|
||||||
},
|
|
||||||
"enable_charging_animation": {
|
|
||||||
"description": "Highlight the bar widget when the device is charging.",
|
|
||||||
"label": "Show Charging Fill"
|
|
||||||
},
|
|
||||||
"enable_clipboard_action": {
|
|
||||||
"description": "Show a quick action to send the clipboard to the device.",
|
|
||||||
"label": "Show Clipboard Action"
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"description": "Display language",
|
|
||||||
"en": "English",
|
|
||||||
"label": "Language",
|
|
||||||
"zh_hans": "简体中文"
|
|
||||||
},
|
|
||||||
"max_recent_images": {
|
|
||||||
"description": "Number of recent images to display.",
|
|
||||||
"label": "Max Recent Images"
|
|
||||||
},
|
|
||||||
"panel_placement": {
|
|
||||||
"attached": "Attached to bar",
|
|
||||||
"description": "How the details panel opens: attached to the bar, or floating.",
|
|
||||||
"floating": "Floating (center)",
|
|
||||||
"label": "Panel Placement",
|
|
||||||
"widget": "Below widget (floating)"
|
|
||||||
},
|
|
||||||
"scan_subdirectories": {
|
|
||||||
"description": "Recursively scan subdirectories of the recent images path.",
|
|
||||||
"label": "Scan Subdirectories"
|
|
||||||
},
|
|
||||||
"show_device_placeholder": {
|
|
||||||
"description": "Show the device graphic in the details panel.",
|
|
||||||
"label": "Show Device Placeholder"
|
|
||||||
},
|
|
||||||
"show_ongoing_media": {
|
|
||||||
"description": "Show media currently playing on the phone.",
|
|
||||||
"label": "Show Ongoing Media"
|
|
||||||
},
|
|
||||||
"state_update_interval": {
|
|
||||||
"description": "Seconds between automatic device state refreshes. 0 disables auto-refresh.",
|
|
||||||
"label": "State Update Interval"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"connected": "Connected",
|
|
||||||
"no_backend": "KDE Connect not running",
|
|
||||||
"no_devices": "No devices",
|
|
||||||
"not_paired": "Not paired",
|
|
||||||
"offline": "Offline",
|
|
||||||
"unavailable": "Unavailable"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"state_update_interval": {
|
||||||
|
"label": "状态刷新间隔",
|
||||||
|
"description": "自动刷新设备状态的间隔秒数。0 表示禁用。"
|
||||||
|
},
|
||||||
|
"enable_charging_animation": {
|
||||||
|
"label": "充电高亮",
|
||||||
|
"description": "设备充电时高亮显示栏部件。"
|
||||||
|
},
|
||||||
|
"custom_image": {
|
||||||
|
"label": "设备头像",
|
||||||
|
"description": "选中设备后在此设置自定义头像图片。留空恢复默认图标。"
|
||||||
|
},
|
||||||
|
"device_alias": {
|
||||||
|
"label": "设备别名",
|
||||||
|
"description": "当前选中设备的自定义显示名称。"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"label": "界面语言",
|
||||||
|
"description": "显示语言。",
|
||||||
|
"en": "English",
|
||||||
|
"zh-hans": "简体中文"
|
||||||
|
},
|
||||||
|
"enable_clipboard_action": {
|
||||||
|
"label": "显示剪贴板操作",
|
||||||
|
"description": "显示一键发送剪贴板到设备的快捷操作。"
|
||||||
|
},
|
||||||
|
"panel_placement": {
|
||||||
|
"label": "面板弹出方式",
|
||||||
|
"description": "详情面板的弹出方式。",
|
||||||
|
"attached": "贴合栏",
|
||||||
|
"floating": "悬浮居中",
|
||||||
|
"widget": "部件下方悬浮"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"unavailable": "不可用",
|
||||||
|
"no_devices": "无设备",
|
||||||
|
"offline": "离线",
|
||||||
|
"connected": "已连接",
|
||||||
|
"not_paired": "未配对",
|
||||||
|
"no_backend": "KDE Connect 未运行"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"ring": "响铃",
|
||||||
|
"ping": "Ping",
|
||||||
|
"clipboard": "剪贴板",
|
||||||
|
"share": "分享",
|
||||||
|
"browse": "浏览",
|
||||||
|
"sms": "短信",
|
||||||
|
"pair": "配对",
|
||||||
|
"accept": "接受",
|
||||||
|
"reject": "拒绝",
|
||||||
|
"unpair": "取消配对",
|
||||||
|
"refresh": "刷新",
|
||||||
|
"switch": "切换设备"
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"ringing": "正在响铃 {name}...",
|
||||||
|
"ping_sent": "已向 {name} 发送 Ping",
|
||||||
|
"clipboard_sent": "剪贴板已发送",
|
||||||
|
"pairing_sent": "已发送配对请求",
|
||||||
|
"paired": "设备已配对",
|
||||||
|
"unpaired": "设备已取消配对",
|
||||||
|
"opening_browser": "正在打开文件浏览器...",
|
||||||
|
"sms_sent": "短信已发送"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"ring_failed": "响铃失败",
|
||||||
|
"ping_failed": "发送 Ping 失败",
|
||||||
|
"clipboard_failed": "发送剪贴板失败",
|
||||||
|
"share_failed": "分享失败",
|
||||||
|
"browse_failed": "浏览设备失败",
|
||||||
|
"pairing_failed": "配对失败",
|
||||||
|
"accept_failed": "接受配对失败",
|
||||||
|
"reject_failed": "拒绝配对失败",
|
||||||
|
"unpair_failed": "取消配对失败",
|
||||||
|
"no_backend": "无后端",
|
||||||
|
"cli_failed": "命令失败"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"title": "手机连接",
|
||||||
|
"empty": "未找到设备。请在 KDE Connect 中配对设备。",
|
||||||
|
"devices_connected": "已连接 {connected} · 已配对 {paired}",
|
||||||
|
"now_playing": "正在播放",
|
||||||
|
"no_media": "无媒体播放",
|
||||||
|
"image_path": "自定义图片",
|
||||||
|
"sms": "发送短信",
|
||||||
|
"sms_dest": "手机号码",
|
||||||
|
"sms_msg": "短信内容",
|
||||||
|
"sms_send": "发送",
|
||||||
|
"battery": "电量",
|
||||||
|
"network": "网络",
|
||||||
|
"pairing": "配对",
|
||||||
|
"type": "类型",
|
||||||
|
"paired": "已配对",
|
||||||
|
"unpaired": "未配对",
|
||||||
|
"unknown": "未知"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,8 +91,8 @@ local function render()
|
|||||||
if reachable and hasCharge then
|
if reachable and hasCharge then
|
||||||
-- battery glyph + percent, colored by level
|
-- battery glyph + percent, colored by level
|
||||||
local batColor = "on_surface"
|
local batColor = "on_surface"
|
||||||
if charge <= 20 then batColor = "error"
|
if charging then batColor = "primary"
|
||||||
elseif charging then batColor = "primary" end
|
elseif charge <= 20 then batColor = "error" end
|
||||||
table.insert(kids, ui.glyph({ name = batteryGlyph(charge, charging),
|
table.insert(kids, ui.glyph({ name = batteryGlyph(charge, charging),
|
||||||
size = 12, color = batColor }))
|
size = 12, color = batColor }))
|
||||||
table.insert(kids, ui.label({
|
table.insert(kids, ui.label({
|
||||||
|
|||||||
Reference in New Issue
Block a user