add calculator plugin (#104)

* feat: add calculator plugin

* add MIT license

* fix(calculator): typo

* fix(calculator): wrong PANEL_ID

* remove LICENSE
This commit is contained in:
Yuuto
2026-07-26 08:39:45 -04:00
committed by GitHub
parent e70da314ce
commit 074247e865
7 changed files with 1191 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
# Calculator
A calculator for the Noctalia bar. It evaluates whole expressions with operator
precedence, offers both a keypad and a typed input line in its panel, and keeps
the last result visible in the bar.
## Plugin
| Field | Value |
| --- | --- |
| ID | `yuuto/calculator` |
| Entries | Bar widget: `bar`; panel: `panel` |
The `panel` entry owns the calculator model and publishes it through Noctalia's
per-plugin state channel. The `bar` widget is a thin client: it reads that state
and opens the panel.
## Usage
Add the `bar` bar widget to your bar. It shows a calculator glyph, plus the last
result once there is one. Left click opens the panel, right click clears the
calculator. On a vertical bar only the glyph is shown, with the current
expression as its tooltip.
The panel evaluates as you type. The small line shows the expression, the large
line the live result, and the copy button next to it puts the result on the
clipboard.
Enter expressions either with the keypad or by typing into the input line, where
Enter evaluates. Pressing `=` keeps the result in the input, so the next
operator continues from it. The keypad uses plain text labels (`AC`, `+/-`,
`DEL`, `*`, `/`) rather than symbol glyphs, so it renders with any bar font.
Open the panel over IPC:
```sh
noctalia msg panel-toggle yuuto/calculator:panel
```
### Expression syntax
Operators are `+`, `-`, `*`, `/` and `^`, with parentheses and the usual
precedence; `^` is right associative. A trailing `%` divides by 100, so
`200*15%` is `30`. The typed input also accepts `×`, `÷` and `−`.
Available constants: `pi`, `tau`, `e`.
Available functions: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`,
`sinh`, `cosh`, `tanh`, `ln`, `log`, `log2`, `log10`, `exp`, `sqrt`, `cbrt`,
`abs`, `floor`, `ceil`, `round`, `trunc`, `sign`, `mod`, `pow`, `hypot`, `min`,
`max`.
`log(x)` is base 10; `log(x, b)` is base `b`. `mod(a, b)` is the remainder, since
`%` means percent here. The trigonometric functions follow the angle unit
setting, shown in the panel header as `RAD` or `DEG`.
## Settings
### Plugin
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `precision` | `int` | `8` | Maximum decimals used when formatting results, 0 to 10. |
| `angle_unit` | `select` | `rad` | Angle unit for the trigonometric functions. |
### Bar Widget
| Setting | Type | Default | Description |
| --- | --- | --- | --- |
| `show_bar_value` | `bool` | `true` | Shows the last result next to the calculator glyph. |
| `max_bar_length` | `int` | `9` | Longer results are shortened with a trailing `~`. |
## IPC
Beyond opening the panel, the `panel` entry accepts three events. The `all`
target addresses every live instance.
```sh
noctalia msg plugin yuuto/calculator:panel all eval "2+3*4"
noctalia msg plugin yuuto/calculator:panel all insert "+8"
noctalia msg plugin yuuto/calculator:panel all clear
```
`eval` replaces the expression and evaluates it, `insert` appends to the current
expression, and `clear` resets the calculator.
## Notes
Expressions are tokenized and parsed by the plugin itself, so evaluating never
runs a shell command and no external tool is required. Results reach the
clipboard through `noctalia.copyToClipboard`.
The panel keeps its model in the plugin state channel rather than in script
locals, so clearing from the bar widget and reopening the panel stay in sync.
## Credits
Keypad layout and feature set are modelled on the v4 Quickshell calculator plugin
by pir0c0pter0 (MIT).
+94
View File
@@ -0,0 +1,94 @@
--!nonstrict
local PANEL_ID = "yuuto/calculator:panel"
local result = "0"
local expression = ""
local hasError = false
local function maxLength()
local value = noctalia.getConfig("max_bar_length")
if type(value) ~= "number" then
return 9
end
return math.clamp(math.floor(value), 3, 20)
end
local function compact(text)
local limit = maxLength()
if #text <= limit then
return text
end
return text:sub(1, math.max(1, limit - 1)) .. "~"
end
local function badge()
if noctalia.getConfig("show_bar_value") == false then
return ""
end
if hasError then
return noctalia.tr("state.error")
end
if result == "" or result == "0" then
return ""
end
return compact(result)
end
local function render()
local container = barWidget.isVertical() and ui.column or ui.row
local text = badge()
local color = hasError and "error" or "on_surface"
barWidget.setTooltip(expression ~= "" and expression or noctalia.tr("bar.tooltip"))
local children = {
ui.glyph({ name = "calculator", color = color }),
}
if text ~= "" and not barWidget.isVertical() then
table.insert(children, ui.label({ text = text, fontWeight = "bold", color = color }))
end
barWidget.render(container({ gap = 6, align = "center" }, children))
end
function update()
render()
end
noctalia.state.watch("calc.result", function(value)
if type(value) == "string" then
result = value
render()
end
end)
noctalia.state.watch("calc.expression", function(value)
if type(value) == "string" then
expression = value
render()
end
end)
noctalia.state.watch("calc.error", function(value)
hasError = value == true
render()
end)
function onClick()
noctalia.togglePanel(PANEL_ID)
end
function onRightClick()
noctalia.state.set("calc.result", "0")
noctalia.state.set("calc.expression", "")
noctalia.state.set("calc.last_expression", "")
noctalia.state.set("calc.error", false)
end
function onIpc(event, _payload)
if event == "toggle" then
noctalia.togglePanel(PANEL_ID)
end
end
+863
View File
@@ -0,0 +1,863 @@
--!nonstrict
local DEG = math.pi / 180
local WIDE_OPERATORS = {
["\u{00d7}"] = "*",
["\u{00f7}"] = "/",
["\u{2212}"] = "-",
["\u{2013}"] = "-",
["\u{00b7}"] = "*",
}
local CONSTANTS = {
pi = math.pi,
tau = math.pi * 2,
e = math.exp(1),
}
local ARITY = {
atan2 = 2,
mod = 2,
pow = 2,
hypot = 2,
}
local function sinh(x)
return (math.exp(x) - math.exp(-x)) / 2
end
local function cosh(x)
return (math.exp(x) + math.exp(-x)) / 2
end
local function tanh(x)
if x > 20 then
return 1
end
if x < -20 then
return -1
end
local a, b = math.exp(x), math.exp(-x)
return (a - b) / (a + b)
end
local function isDigit(c)
return c >= "0" and c <= "9"
end
local function isAlpha(c)
return (c >= "a" and c <= "z") or (c >= "A" and c <= "Z") or c == "_"
end
local function tokenize(src)
local tokens = {}
local i = 1
local n = #src
while i <= n do
local wide = WIDE_OPERATORS[src:sub(i, i + 1)] or WIDE_OPERATORS[src:sub(i, i + 2)]
if wide ~= nil then
table.insert(tokens, { kind = "op", text = wide })
i += WIDE_OPERATORS[src:sub(i, i + 1)] ~= nil and 2 or 3
continue
end
local c = src:sub(i, i)
if c == " " or c == "\t" then
i += 1
elseif isDigit(c) or (c == "." and isDigit(src:sub(i + 1, i + 1))) then
local start = i
while i <= n and isDigit(src:sub(i, i)) do
i += 1
end
if src:sub(i, i) == "." then
i += 1
while i <= n and isDigit(src:sub(i, i)) do
i += 1
end
end
local mark = i
if src:sub(i, i):lower() == "e" then
local j = i + 1
local sign = src:sub(j, j)
if sign == "+" or sign == "-" then
j += 1
end
if isDigit(src:sub(j, j)) then
i = j
while i <= n and isDigit(src:sub(i, i)) do
i += 1
end
else
i = mark
end
end
local value = tonumber(src:sub(start, i - 1))
if value == nil then
return nil
end
table.insert(tokens, { kind = "num", value = value })
elseif isAlpha(c) then
local start = i
while i <= n and (isAlpha(src:sub(i, i)) or isDigit(src:sub(i, i))) do
i += 1
end
table.insert(tokens, { kind = "name", text = src:sub(start, i - 1):lower() })
elseif c == "(" or c == ")" then
table.insert(tokens, { kind = c })
i += 1
elseif c == "," or c == ";" then
table.insert(tokens, { kind = "," })
i += 1
elseif c == "+" or c == "-" or c == "*" or c == "/" or c == "^" or c == "%" then
table.insert(tokens, { kind = "op", text = c })
i += 1
else
return nil
end
end
return tokens
end
local function makeFunctions(degrees)
local toAngle = degrees and function(x)
return x * DEG
end or function(x)
return x
end
local fromAngle = degrees and function(x)
return x / DEG
end or function(x)
return x
end
return {
sin = function(a)
return math.sin(toAngle(a[1]))
end,
cos = function(a)
return math.cos(toAngle(a[1]))
end,
tan = function(a)
return math.tan(toAngle(a[1]))
end,
asin = function(a)
return fromAngle(math.asin(a[1]))
end,
acos = function(a)
return fromAngle(math.acos(a[1]))
end,
atan = function(a)
if #a >= 2 then
return fromAngle(math.atan2(a[1], a[2]))
end
return fromAngle(math.atan(a[1]))
end,
atan2 = function(a)
return fromAngle(math.atan2(a[1], a[2]))
end,
sinh = function(a)
return sinh(a[1])
end,
cosh = function(a)
return cosh(a[1])
end,
tanh = function(a)
return tanh(a[1])
end,
ln = function(a)
return math.log(a[1])
end,
log = function(a)
if #a >= 2 then
return math.log(a[1]) / math.log(a[2])
end
return math.log(a[1], 10)
end,
log2 = function(a)
return math.log(a[1], 2)
end,
log10 = function(a)
return math.log(a[1], 10)
end,
exp = function(a)
return math.exp(a[1])
end,
sqrt = function(a)
return math.sqrt(a[1])
end,
cbrt = function(a)
local x = a[1]
if x < 0 then
return -((-x) ^ (1 / 3))
end
return x ^ (1 / 3)
end,
abs = function(a)
return math.abs(a[1])
end,
floor = function(a)
return math.floor(a[1])
end,
ceil = function(a)
return math.ceil(a[1])
end,
round = function(a)
return math.round(a[1])
end,
trunc = function(a)
local x = a[1]
return x >= 0 and math.floor(x) or math.ceil(x)
end,
sign = function(a)
return math.sign(a[1])
end,
mod = function(a)
return a[1] % a[2]
end,
pow = function(a)
return a[1] ^ a[2]
end,
hypot = function(a)
return math.sqrt(a[1] * a[1] + a[2] * a[2])
end,
min = function(a)
return math.min(table.unpack(a))
end,
max = function(a)
return math.max(table.unpack(a))
end,
}
end
local function evaluate(source, degrees)
local tokens = tokenize(source)
if tokens == nil or #tokens == 0 then
return nil
end
local functions = makeFunctions(degrees)
local pos = 1
local failed = false
local function peek()
return tokens[pos]
end
local function fail()
failed = true
return 0
end
local parseExpression
local parseUnary
local function parsePrimary()
local token = peek()
if token == nil then
return fail()
end
if token.kind == "num" then
pos += 1
return token.value
end
if token.kind == "(" then
pos += 1
local value = parseExpression()
local closing = peek()
if closing == nil or closing.kind ~= ")" then
return fail()
end
pos += 1
return value
end
if token.kind == "name" then
pos += 1
local name = token.text
local following = peek()
if following ~= nil and following.kind == "(" then
local fn = functions[name]
if fn == nil then
return fail()
end
pos += 1
local args = {}
if peek() ~= nil and peek().kind ~= ")" then
table.insert(args, parseExpression())
while peek() ~= nil and peek().kind == "," do
pos += 1
table.insert(args, parseExpression())
end
end
local closing = peek()
if closing == nil or closing.kind ~= ")" then
return fail()
end
pos += 1
if #args < (ARITY[name] or 1) then
return fail()
end
return fn(args)
end
local constant = CONSTANTS[name]
if constant ~= nil then
return constant
end
return fail()
end
return fail()
end
local function parsePostfix()
local value = parsePrimary()
while true do
local token = peek()
if token ~= nil and token.kind == "op" and token.text == "%" then
pos += 1
value /= 100
else
break
end
end
return value
end
local function parsePower()
local base = parsePostfix()
local token = peek()
if token ~= nil and token.kind == "op" and token.text == "^" then
pos += 1
return base ^ parseUnary()
end
return base
end
function parseUnary()
local token = peek()
if token ~= nil and token.kind == "op" and (token.text == "-" or token.text == "+") then
pos += 1
local value = parseUnary()
return token.text == "-" and -value or value
end
return parsePower()
end
local function parseTerm()
local value = parseUnary()
while true do
local token = peek()
if token ~= nil and token.kind == "op" and (token.text == "*" or token.text == "/") then
pos += 1
local rhs = parseUnary()
if token.text == "*" then
value *= rhs
else
if rhs == 0 then
return fail()
end
value /= rhs
end
elseif token ~= nil and token.kind == "(" then
value *= parseUnary()
else
break
end
end
return value
end
function parseExpression()
local value = parseTerm()
while true do
local token = peek()
if token ~= nil and token.kind == "op" and (token.text == "+" or token.text == "-") then
pos += 1
local rhs = parseTerm()
if token.text == "+" then
value += rhs
else
value -= rhs
end
else
break
end
end
return value
end
local ok, value = pcall(parseExpression)
if not ok or failed or pos <= #tokens then
return nil
end
if type(value) ~= "number" or value ~= value or value == math.huge or value == -math.huge then
return nil
end
return value
end
local function formatNumber(value, precision)
if value == 0 then
return "0"
end
local magnitude = math.abs(value)
if magnitude >= 1e16 or magnitude < 1e-9 then
local text = string.format("%." .. math.min(precision, 8) .. "e", value)
local mantissa, exponent = text:match("^(.-)e([-+]%d+)$")
if mantissa ~= nil then
if mantissa:find("%.") then
mantissa = mantissa:gsub("0+$", ""):gsub("%.$", "")
end
local sign, digits = exponent:match("^([-+])0*(%d+)$")
return mantissa .. "e" .. (sign == "-" and "-" or "") .. digits
end
return text
end
local text = string.format("%." .. precision .. "f", value)
if text:find("%.") then
text = text:gsub("0+$", ""):gsub("%.$", "")
end
if text == "-0" then
return "0"
end
return text
end
local expression = ""
local lastExpression = ""
local result = "0"
local lastPreview = nil
local hasError = false
local inputKey = 0
local function precision()
local value = noctalia.getConfig("precision")
if type(value) ~= "number" then
return 8
end
return math.clamp(math.floor(value), 0, 10)
end
local function useDegrees()
return noctalia.getConfig("angle_unit") == "deg"
end
local function restore()
local storedResult = noctalia.state.get("calc.result")
if type(storedResult) == "string" and storedResult ~= "" then
result = storedResult
end
local storedExpression = noctalia.state.get("calc.expression")
if type(storedExpression) == "string" then
expression = storedExpression
end
local storedLast = noctalia.state.get("calc.last_expression")
if type(storedLast) == "string" then
lastExpression = storedLast
end
hasError = noctalia.state.get("calc.error") == true
lastPreview = nil
end
local function publish()
noctalia.state.set("calc.result", result)
noctalia.state.set("calc.expression", expression)
noctalia.state.set("calc.last_expression", lastExpression)
noctalia.state.set("calc.error", hasError)
end
local function preview()
if expression == "" then
return nil
end
local value = evaluate(expression, useDegrees())
if value == nil then
return nil
end
return formatNumber(value, precision())
end
local function displayValue()
if hasError then
return noctalia.tr("state.error")
end
if expression == "" then
return result
end
local current = preview()
if current ~= nil then
lastPreview = current
end
return current or lastPreview or result
end
local function toggleSign()
if expression == "" then
expression = "-"
return
end
local head, tail = expression:match("^(.-)([%d%.]+)$")
if tail == nil then
local last = expression:sub(-1)
if last:match("[%+%-%*/%^%(]") then
expression ..= "-"
end
return
end
local before = head:sub(-1)
if before == "-" then
local previous = head:sub(-2, -2)
if head == "-" or previous:match("[%+%-%*/%^%(,]") then
expression = head:sub(1, -2) .. tail
return
end
end
expression = head .. "-" .. tail
end
local render
local function clearAll()
expression = ""
lastExpression = ""
result = "0"
lastPreview = nil
hasError = false
inputKey += 1
end
local function commit()
if expression == "" then
return
end
local value = evaluate(expression, useDegrees())
if value == nil then
hasError = true
lastExpression = expression
expression = ""
lastPreview = nil
inputKey += 1
return
end
result = formatNumber(value, precision())
lastPreview = result
lastExpression = expression
expression = result
hasError = false
inputKey += 1
end
local function press(action, literal)
if hasError and action ~= "clear" then
hasError = false
expression = ""
lastExpression = ""
end
if action == "clear" then
clearAll()
elseif action == "delete" then
expression = expression:sub(1, -2)
inputKey += 1
elseif action == "sign" then
toggleSign()
inputKey += 1
elseif action == "equals" then
commit()
else
expression ..= literal
inputKey += 1
end
publish()
render()
end
local KEYPAD = {
{
{ label = "AC", handler = "onCalcClear", variant = "destructive" },
{ label = "+/-", handler = "onCalcSign", variant = "secondary" },
{ label = "%", handler = "onCalcPercent", variant = "secondary" },
{ label = "DEL", handler = "onCalcDelete", variant = "secondary" },
},
{
{ label = "7", handler = "onCalcSeven" },
{ label = "8", handler = "onCalcEight" },
{ label = "9", handler = "onCalcNine" },
{ label = "/", handler = "onCalcDivide", variant = "secondary" },
},
{
{ label = "4", handler = "onCalcFour" },
{ label = "5", handler = "onCalcFive" },
{ label = "6", handler = "onCalcSix" },
{ label = "*", handler = "onCalcMultiply", variant = "secondary" },
},
{
{ label = "1", handler = "onCalcOne" },
{ label = "2", handler = "onCalcTwo" },
{ label = "3", handler = "onCalcThree" },
{ label = "-", handler = "onCalcMinus", variant = "secondary" },
},
{
{ label = "0", handler = "onCalcZero", grow = 2 },
{ label = ".", handler = "onCalcDecimal" },
{ label = "+", handler = "onCalcPlus", variant = "secondary" },
},
{
{ label = "(", handler = "onCalcOpen", variant = "ghost" },
{ label = ")", handler = "onCalcClose", variant = "ghost" },
{ label = "=", handler = "onCalcEquals", variant = "primary", grow = 2 },
},
}
local function keypadRow(row, rowIndex)
local buttons = {}
for index, spec in ipairs(row) do
table.insert(
buttons,
ui.button({
key = "k" .. rowIndex .. "-" .. index,
text = spec.label,
variant = spec.variant or "default",
fontSize = 15,
height = 34,
flexGrow = spec.grow or 1,
onClick = spec.handler,
})
)
end
return ui.row({ key = "row" .. rowIndex, gap = 6 }, buttons)
end
function render()
local shown = displayValue()
local hint = hasError and lastExpression or (expression ~= "" and expression or lastExpression)
local atRest = shown == "0" and expression == "" and lastExpression == ""
local canCopy = not hasError and shown ~= "" and not atRest
local keypad = {}
for index, row in ipairs(KEYPAD) do
table.insert(keypad, keypadRow(row, index))
end
panel.render(ui.column({ gap = 10, padding = 14 }, {
ui.row({ gap = 8, align = "center" }, {
ui.glyph({ name = "calculator", size = 16, color = "primary" }),
ui.label({
text = noctalia.tr("panel.title"),
fontSize = 15,
fontWeight = "bold",
color = "on_surface",
flexGrow = 1,
}),
ui.label({
text = noctalia.tr(useDegrees() and "panel.deg" or "panel.rad"),
fontSize = 11,
color = "on_surface/0.55",
}),
}),
ui.column({ gap = 2, padding = 10, fill = "primary/0.08", radius = 10 }, {
ui.label({
text = hint,
fontSize = 11,
color = "on_surface/0.6",
textAlign = "right",
maxLines = 1,
}),
ui.row({ gap = 6, align = "center" }, {
ui.label({
text = shown,
fontSize = 30,
fontWeight = "bold",
color = hasError and "error" or "on_surface",
textAlign = "right",
maxLines = 1,
flexGrow = 1,
}),
ui.button({
key = "copy",
glyph = "copy",
glyphSize = 15,
variant = "ghost",
controlSize = "sm",
contentAlign = "center",
tooltip = noctalia.tr("panel.copy"),
visible = canCopy,
onClick = "onCalcCopy",
}),
}),
}),
ui.input({
key = "expr-" .. inputKey,
value = expression,
placeholder = noctalia.tr("panel.placeholder"),
fontSize = 13,
controlSize = "sm",
focus = true,
onChange = "onCalcInputChange",
onSubmit = "onCalcInputSubmit",
}),
ui.column({ gap = 6 }, keypad),
}))
end
function onCalcClear()
press("clear")
end
function onCalcSign()
press("sign")
end
function onCalcPercent()
press("append", "%")
end
function onCalcDelete()
press("delete")
end
function onCalcZero()
press("append", "0")
end
function onCalcOne()
press("append", "1")
end
function onCalcTwo()
press("append", "2")
end
function onCalcThree()
press("append", "3")
end
function onCalcFour()
press("append", "4")
end
function onCalcFive()
press("append", "5")
end
function onCalcSix()
press("append", "6")
end
function onCalcSeven()
press("append", "7")
end
function onCalcEight()
press("append", "8")
end
function onCalcNine()
press("append", "9")
end
function onCalcDecimal()
press("append", ".")
end
function onCalcPlus()
press("append", "+")
end
function onCalcMinus()
press("append", "-")
end
function onCalcMultiply()
press("append", "*")
end
function onCalcDivide()
press("append", "/")
end
function onCalcOpen()
press("append", "(")
end
function onCalcClose()
press("append", ")")
end
function onCalcEquals()
press("equals")
end
function onCalcCopy()
local shown = displayValue()
if hasError or shown == "" then
return
end
if noctalia.copyToClipboard(shown, "text/plain") then
noctalia.notify(noctalia.tr("panel.title"), noctalia.tr("panel.copied", { value = shown }))
end
end
function onCalcInputChange(value)
expression = value
hasError = false
publish()
render()
end
function onCalcInputSubmit()
press("equals")
end
function onOpen(_context)
restore()
inputKey += 1
render()
end
function onIpc(event, payload)
if event == "clear" then
clearAll()
elseif event == "eval" and type(payload) == "string" then
expression = payload
commit()
elseif event == "insert" and type(payload) == "string" then
expression ..= payload
inputKey += 1
else
return
end
publish()
render()
end
noctalia.state.watch("calc.expression", function(value)
if type(value) ~= "string" or value == expression then
return
end
restore()
inputKey += 1
render()
end)
restore()
publish()
+59
View File
@@ -0,0 +1,59 @@
id = "yuuto/calculator"
name = "Calculator"
version = "1.0.0"
plugin_api = 3
author = "yuuto"
license = "MIT"
icon = "calculator"
description = "A theme-aware calculator with a bar widget and a panel: full expression evaluation, a button grid, and typed input."
dependencies = []
tags = ["utility", "productivity", "bar", "panel"]
[[setting]]
key = "precision"
type = "int"
label_key = "settings.precision.label"
description_key = "settings.precision.description"
default = 8
min = 0
max = 10
[[setting]]
key = "angle_unit"
type = "select"
label_key = "settings.angle_unit.label"
description_key = "settings.angle_unit.description"
default = "rad"
options = [
{ value = "rad", label_key = "settings.angle_unit.options.rad" },
{ value = "deg", label_key = "settings.angle_unit.options.deg" },
]
[[widget]]
id = "bar"
entry = "bar.luau"
[[widget.setting]]
key = "show_bar_value"
type = "bool"
label_key = "settings.show_bar_value.label"
description_key = "settings.show_bar_value.description"
default = true
[[widget.setting]]
key = "max_bar_length"
type = "int"
label_key = "settings.max_bar_length.label"
description_key = "settings.max_bar_length.description"
default = 9
min = 3
max = 20
[[panel]]
id = "panel"
entry = "panel.luau"
width = 320
height = 452
placement = "attached"
position = "auto"
open_near_click = true
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

+38
View File
@@ -0,0 +1,38 @@
{
"bar": {
"tooltip": "Rechner"
},
"panel": {
"title": "Rechner",
"placeholder": "Ausdruck eingeben, Enter drücken",
"copy": "Ergebnis kopieren",
"copied": "Kopiert: {value}",
"rad": "RAD",
"deg": "GRAD"
},
"state": {
"error": "Fehler"
},
"settings": {
"precision": {
"label": "Dezimalgenauigkeit",
"description": "Maximale Anzahl Nachkommastellen bei der Ergebnisformatierung."
},
"angle_unit": {
"label": "Winkeleinheit",
"description": "Einheit, die die trigonometrischen Funktionen verwenden.",
"options": {
"rad": "Bogenmaß",
"deg": "Grad"
}
},
"show_bar_value": {
"label": "Wert in der Leiste anzeigen",
"description": "Zeigt das letzte Ergebnis neben dem Rechner-Symbol an."
},
"max_bar_length": {
"label": "Maximale Länge in der Leiste",
"description": "Längere Ergebnisse werden in der Leiste mit Auslassungspunkten gekürzt."
}
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"bar": {
"tooltip": "Calculator"
},
"panel": {
"title": "Calculator",
"placeholder": "Type an expression, press Enter",
"copy": "Copy result",
"copied": "Copied {value}",
"rad": "RAD",
"deg": "DEG"
},
"state": {
"error": "Error"
},
"settings": {
"precision": {
"label": "Decimal precision",
"description": "Maximum number of decimals used when formatting results."
},
"angle_unit": {
"label": "Angle unit",
"description": "Unit used by the trigonometric functions.",
"options": {
"rad": "Radians",
"deg": "Degrees"
}
},
"show_bar_value": {
"label": "Show value in bar",
"description": "Display the last result next to the calculator icon."
},
"max_bar_length": {
"label": "Maximum bar length",
"description": "Longer results are shortened with an ellipsis in the bar."
}
}
}