claude companion: add English translations for user facing strings (#84)

This commit is contained in:
Jarred Robinson
2026-07-23 17:54:19 -04:00
committed by GitHub
parent 0a144f68fc
commit 8ee41fcfd6
6 changed files with 137 additions and 54 deletions
+22 -15
View File
@@ -14,6 +14,12 @@ local function shq(s)
return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
end
-- User-facing display strings live in translations/<lang>.json; resolve them at
-- call time (the noctalia global is ready by then). Model-facing prompts
-- (ASK_NOTE, SYSTEM_NOTE below) are deliberately NOT translated — they are
-- instructions to the model, not UI, and rewording them can shift its behavior.
local function tr(key, args) return noctalia.tr(key, args) end
-- A double-quoted sh argument: unlike shq it lets the shell expand $VAR (we need
-- $HOME in the shim path so nothing user-specific is hardcoded). ONLY use this on
-- fixed, trusted strings — never on user input, which must go through shq().
@@ -150,7 +156,7 @@ end
-- 401 whenever no terminal session has run recently. Two layers, both fail-open:
-- a pre-flight that skips the launch when the token is positively expired, and
-- an error-text match that swaps the raw API error for the remedy.
local AUTH_REMEDY = "Claude login expired — start a Claude session in a terminal to refresh it, then re-ask."
-- (remedy text: tr("notify.auth_remedy"))
local CREDS_PATH = "~/.claude/.credentials.json"
@@ -220,14 +226,14 @@ local function trim(s) return (s:gsub("^%s+", ""):gsub("%s+$", "")) end
local function answer_row()
local a = noctalia.state.get("claude.answer")
if type(a) ~= "table" or type(a.text) ~= "string" or a.text == "" then return nil end
return { id = "answer", title = "Show last answer", subtitle = a.q, glyph = "message-2" }
return { id = "answer", title = tr("launcher.show_answer"), subtitle = a.q, glyph = "message-2" }
end
function onQuery(query)
local text = trim(query)
if text == "" then
local results = {
{ id = "continue", title = "Resume last Claude session", glyph = "robot" },
{ id = "continue", title = tr("launcher.continue"), glyph = "robot" },
}
results[#results + 1] = answer_row()
launcher.setResults(query, results)
@@ -237,19 +243,19 @@ function onQuery(query)
if ask ~= nil then
if ask == "" then
local results = {
{ id = "_askhint", title = "Ask Claude (one-shot)", subtitle = "Type a question after ?", glyph = "message-dots" },
{ id = "_askhint", title = tr("launcher.ask"), subtitle = tr("launcher.ask_hint"), glyph = "message-dots" },
}
results[#results + 1] = answer_row()
launcher.setResults(query, results)
else
launcher.setResults(query, {
{ id = "ask:" .. ask, title = "Ask Claude (one-shot)", subtitle = ask, glyph = "message-dots" },
{ id = "ask:" .. ask, title = tr("launcher.ask"), subtitle = ask, glyph = "message-dots" },
})
end
return
end
launcher.setResults(query, {
{ id = "task:" .. text, title = "Launch Claude Code", subtitle = text, glyph = "robot" },
{ id = "task:" .. text, title = tr("launcher.launch"), subtitle = text, glyph = "robot" },
})
end
@@ -278,8 +284,9 @@ function onActivate(id)
-- and the remedy needs a terminal anyway — so say so now and skip the
-- launch. No claude.state touch: no ask session ever starts.
if token_expired() then
publish_answer(ask, AUTH_REMEDY, true)
if not panel_showing() then noctalia.notifyError("Claude", AUTH_REMEDY) end
local remedy = tr("notify.auth_remedy")
publish_answer(ask, remedy, true)
if not panel_showing() then noctalia.notifyError(tr("notify.title"), remedy) end
return
end
set_state(EVENT.turn_start)
@@ -295,26 +302,26 @@ function onActivate(id)
elseif ev.kind == EVENT.turn_end then
local final = (ev.text and ev.text ~= "") and ev.text or acc
if final == "" then
noctalia.notify("Claude", "(no output)")
noctalia.notify(tr("notify.title"), tr("notify.no_output"))
else
publish_answer(ask, final, false)
if not panel_showing() then
local body, clipped = preview(final)
if clipped then body = body .. "\nFull answer: click the bar pulse" end
noctalia.notify("Claude", body)
if clipped then body = body .. "\n" .. tr("notify.full_answer_hint") end
noctalia.notify(tr("notify.title"), body)
end
end
elseif ev.kind == EVENT.error then
local final = (ev.text and ev.text ~= "") and ev.text or acc
if final == "" then final = "ask failed" end
if final == "" then final = tr("notify.ask_failed") end
-- a 401 that slipped past the pre-flight: deliver the remedy, not the
-- raw API error blob
if is_auth_error(final) then final = AUTH_REMEDY end
if is_auth_error(final) then final = tr("notify.auth_remedy") end
publish_answer(ask, final, true)
if not panel_showing() then
local body, clipped = preview(final)
if clipped then body = body .. "\nFull answer: click the bar pulse" end
noctalia.notifyError("Claude", body)
if clipped then body = body .. "\n" .. tr("notify.full_answer_hint") end
noctalia.notifyError(tr("notify.title"), body)
end
end
end)