Word parameterized messages at the sender with omarchy-text and translate only exact text in the shell
This commit is contained in:
1 parent
619979a716
commit
051952600d
38 files changed
+198
-156
No files matched your search
@@ -103,7 +103,7 @@ Exceptions are allowed for migration and package-helper scripts where the helper
|
||||
- `default/region/china/pacman.sed` puts a `.omarchy.cn` twin ahead of every `.omarchy.org` server; `omarchy-refresh-pacman`, `install/post-install/pacman.sh` and `omarchy-region` apply it. Nothing in `omarchy update` or migrations may change the region or `/etc/locale.conf`, and a migration that edits an `.omarchy.org` `Server` line must edit its `.cn` twin the same way.
|
||||
- China-only menu rows carry `"when":"[[ \"$(omarchy-region)\" == \"China\" ]]"` on every row, not only the submenu, because search checks each row's own guard.
|
||||
- Chinese menu labels live in `default/omarchy/omarchy-menu.zh-cn.jsonc`, merged when `LANG` starts with `zh_CN`; give a new menu row with translatable words a label there.
|
||||
- Shell text goes through `I18n.tr("English %1", arg)` from `qs.Commons`, with its Chinese in `shell/Commons/I18nZh.js` keyed by the English; notifications and OSD messages are translated where the shell shows them, so bash senders pass English. `test/shell.d/shell-i18n-test.sh` fails on a missing entry.
|
||||
- Shell text goes through `I18n.tr("English %1", arg)` from `qs.Commons`, with its Chinese in `shell/Commons/I18nZh.js` keyed by the English. The shell translates fixed notification and OSD text by exact match; a sender whose message carries a name or number words it with `omarchy-text "English %1" "$arg"` (Python: `text()`), which reads the same table. `test/shell.d/shell-i18n-test.sh` fails on a missing entry.
|
||||
- `rime-ice-installer` is built from `omarchy-pkgs` with `omarchy-offline.patch` until it ships in the Omarchy repo.
|
||||
|
||||
# Config Structure
|
||||
|
||||
@@ -85,7 +85,7 @@ finish() {
|
||||
fail() {
|
||||
if [[ $events == "true" ]]; then
|
||||
printf '@@omarchy error %s\n' "$1"
|
||||
omarchy-notification-send -g -u critical "Couldn't add the $name account" "$1"
|
||||
omarchy-notification-send -g -u critical "$(omarchy-text "Couldn't add the %1 account" "$name")" "$1"
|
||||
else
|
||||
echo "$1" >&2
|
||||
fi
|
||||
@@ -113,9 +113,9 @@ interruptible() {
|
||||
}
|
||||
|
||||
if omarchy-cmd-missing "$provider"; then
|
||||
say "Installing the $name CLI…"
|
||||
say "$(omarchy-text "Installing the %1 CLI…" "$name")"
|
||||
if [[ $events == "true" ]]; then
|
||||
mise use -g "$package" >/dev/null 2>&1 || fail "Installing the $name CLI didn't finish."
|
||||
mise use -g "$package" >/dev/null 2>&1 || fail "$(omarchy-text "Installing the %1 CLI didn't finish." "$name")"
|
||||
else
|
||||
mise use -g "$package" || exit 1
|
||||
fi
|
||||
@@ -150,26 +150,26 @@ if [[ -n $reauth ]]; then
|
||||
else
|
||||
account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }')
|
||||
fi
|
||||
[[ -n $account_home ]] || fail "No $name account named $reauth."
|
||||
[[ -n $account_home ]] || fail "$(omarchy-text "No %1 account named %2." "$name" "$reauth")"
|
||||
|
||||
if [[ $account_home == "$HOME/.$provider" ]]; then
|
||||
say "Sign in to $name again in the browser window that opens."
|
||||
login || fail "The $name sign-in didn't finish."
|
||||
say "$(omarchy-text "Sign in to %1 again in the browser window that opens." "$name")"
|
||||
login || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
else
|
||||
say "Sign in again as this account in the private window that opens."
|
||||
say "$(omarchy-text "Sign in again as this account in the private window that opens.")"
|
||||
private_browser
|
||||
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "The $name sign-in didn't finish."
|
||||
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
fi
|
||||
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
finish "Signed in to $name again."
|
||||
finish "$(omarchy-text "Signed in to %1 again." "$name")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The first subscription signs in where the CLI always looks, through your
|
||||
# normal browser: nothing is signed in there yet that it could be mistaken for.
|
||||
if ! primary_signed_in "$provider"; then
|
||||
say "Sign in to $name in the browser window that opens."
|
||||
say "$(omarchy-text "Sign in to %1 in the browser window that opens." "$name")"
|
||||
if login && primary_signed_in "$provider"; then
|
||||
# The first agent signed in on a machine with none chosen becomes the
|
||||
# default, so `omarchy agent` and the panel's starter prompts have one to
|
||||
@@ -179,9 +179,9 @@ if ! primary_signed_in "$provider"; then
|
||||
printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent"
|
||||
fi
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
finish "Signed in to $name."
|
||||
finish "$(omarchy-text "Signed in to %1." "$name")"
|
||||
else
|
||||
fail "The $name sign-in didn't finish."
|
||||
fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
fi
|
||||
else
|
||||
if [[ -z $label && $events == "false" && -t 0 ]]; then
|
||||
@@ -189,7 +189,7 @@ else
|
||||
fi
|
||||
|
||||
if [[ $events == "true" ]]; then
|
||||
say "Sign in as the account you're adding in the private window that opens."
|
||||
say "$(omarchy-text "Sign in as the account you're adding in the private window that opens.")"
|
||||
else
|
||||
cat <<EOF
|
||||
|
||||
@@ -201,7 +201,7 @@ Don't log out of the $name CLI to do this: logging out revokes the saved sign-in
|
||||
EOF
|
||||
fi
|
||||
|
||||
home=$(omarchy-agent-account-state begin "$provider") || fail "Couldn't set up a home for the new account."
|
||||
home=$(omarchy-agent-account-state begin "$provider") || fail "$(omarchy-text "Couldn't set up a home for the new account.")"
|
||||
|
||||
private_browser
|
||||
interruptible env BROWSER="$browser" "$home_env=$home" "${login_command[@]}"
|
||||
@@ -211,9 +211,9 @@ EOF
|
||||
added=$(jq -r '.label + (if .email != "" then " (" + .email + ")" else "" end)' <<<"$account")
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
if [[ $events == "true" ]]; then
|
||||
finish "Added $added."
|
||||
finish "$(omarchy-text "Added %1." "$added")"
|
||||
else
|
||||
finish "Added $added."
|
||||
finish "$(omarchy-text "Added %1." "$added")"
|
||||
echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -73,6 +73,11 @@ class AccountError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def text(english: str, *args: object) -> str:
|
||||
"""English with %1.. filled, in the session's language, from the shell's table."""
|
||||
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- paths
|
||||
|
||||
|
||||
@@ -335,12 +340,12 @@ def begin(provider: str) -> Path:
|
||||
def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
|
||||
pending = pending.resolve()
|
||||
if pending.parent != (accounts_root() / provider / ".pending").resolve():
|
||||
raise AccountError(f"{pending} is not a pending {PROVIDERS[provider]['name']} login")
|
||||
raise AccountError(text("%1 is not a pending %2 login", pending, PROVIDERS[provider]['name']))
|
||||
|
||||
found = identity(provider, pending)
|
||||
if not found["accountId"]:
|
||||
shutil.rmtree(pending, ignore_errors=True)
|
||||
raise AccountError("The login didn't finish, so no account was added.")
|
||||
raise AccountError(text("The login didn't finish, so no account was added."))
|
||||
|
||||
with Locked(provider) as registry:
|
||||
# Each home is asked who it is now, not trusted from when it was added: a
|
||||
@@ -350,10 +355,10 @@ def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
|
||||
for account in registry["accounts"]:
|
||||
if account.get("accountId") == found["accountId"]:
|
||||
shutil.rmtree(pending, ignore_errors=True)
|
||||
raise AccountError(
|
||||
f"That's {account['label']} ({found['email'] or 'already added'}). "
|
||||
"Try again, and sign in as the other account in the private window."
|
||||
)
|
||||
raise AccountError(text(
|
||||
"That's %1 (%2). Try again, and sign in as the other account in the private window.",
|
||||
account["label"], found["email"] or text("already added"),
|
||||
))
|
||||
|
||||
label = label.strip() or found["email"].split("@")[0] or "Account"
|
||||
account_id = unique_id(provider, registry, label)
|
||||
@@ -395,11 +400,11 @@ def unique_id(provider: str, registry: dict[str, Any], label: str, keep: dict[st
|
||||
def rename(provider: str, target: str, label: str) -> dict[str, Any]:
|
||||
label = label.strip()
|
||||
if not label:
|
||||
raise AccountError("Give the account a name")
|
||||
raise AccountError(text("Give the account a name"))
|
||||
with Locked(provider) as registry:
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
new_id = unique_id(provider, registry, label, keep=account)
|
||||
if not account.get("primary") and not account.get("home"):
|
||||
account["home"] = str(account_home(provider, account))
|
||||
@@ -428,7 +433,7 @@ def use(provider: str, target: str) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
target = ids[(ids.index(previous["id"]) + 1) % len(ids)]
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
registry["active"] = account["id"]
|
||||
registry["alert"] = ""
|
||||
if not account.get("primary"):
|
||||
@@ -456,12 +461,12 @@ def remove(provider: str, target: str) -> dict[str, Any]:
|
||||
with Locked(provider) as registry:
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
if account.get("primary"):
|
||||
raise AccountError(f"{account['label']} is the login in {primary_home(provider)}; it can't be removed here.")
|
||||
raise AccountError(text("%1 is the login in %2; it can't be removed here.", account["label"], primary_home(provider)))
|
||||
home = account_home(provider, account)
|
||||
if home_in_use(provider, home):
|
||||
raise AccountError(f"A running {PROVIDERS[provider]['name']} session uses {account['label']}. Quit it first, then remove the account.")
|
||||
raise AccountError(text("A running %1 session uses %2. Quit it first, then remove the account.", PROVIDERS[provider]['name'], account["label"]))
|
||||
# Only the account's own directory goes. Its links point into the primary
|
||||
# home, and rmtree removes a link without following it.
|
||||
if home.resolve().parent == (accounts_root() / provider).resolve():
|
||||
@@ -474,14 +479,14 @@ def remove(provider: str, target: str) -> dict[str, Any]:
|
||||
|
||||
def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]:
|
||||
if mode not in SWITCH_MODES:
|
||||
raise AccountError("Switch mode is manual or auto")
|
||||
raise AccountError(text("Switch mode is manual or auto"))
|
||||
with Locked(provider) as registry:
|
||||
registry["switch"] = mode
|
||||
if threshold is not None:
|
||||
try:
|
||||
registry["threshold"] = max(50, min(100, int(threshold)))
|
||||
except ValueError:
|
||||
raise AccountError("The threshold is a percentage between 50 and 100")
|
||||
raise AccountError(text("The threshold is a percentage between 50 and 100"))
|
||||
registry["alert"] = ""
|
||||
return dict(registry)
|
||||
|
||||
@@ -556,10 +561,10 @@ def duration(delta: dt.timedelta) -> str:
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
days, hours = divmod(hours, 24)
|
||||
if days:
|
||||
return f"{days}d {hours}h"
|
||||
return text("%1d %2h", days, hours)
|
||||
if hours:
|
||||
return f"{hours}h {minutes}m"
|
||||
return f"{minutes}m"
|
||||
return text("%1h %2m", hours, minutes)
|
||||
return text("%1m", minutes)
|
||||
|
||||
|
||||
# The whole switching policy, with no I/O: given the registry and the per
|
||||
@@ -659,21 +664,23 @@ def autoswitch(provider: str, now: dt.datetime | None = None) -> bool:
|
||||
threshold = registry["threshold"]
|
||||
if decision["action"] == "exhausted":
|
||||
soonest = decision["soonest"]
|
||||
when = f" {soonest[1]['label']} resets in {duration(soonest[0] - now)}." if soonest else ""
|
||||
notify(f"All {name} accounts are over {threshold}%", f"Staying on {source['label']}.{when}")
|
||||
staying = text("Staying on %1.", source["label"])
|
||||
if soonest:
|
||||
staying = text("Staying on %1. %2 resets in %3.", source["label"], soonest[1]["label"], duration(soonest[0] - now))
|
||||
notify(text("All %1 accounts are over %2%", name, threshold), staying)
|
||||
return False
|
||||
|
||||
target = decision["to"]
|
||||
headline = f"{source['label']} is at {round(decision['fromPeak'])}% of a {name} limit"
|
||||
headline = text("%1 is at %2% of a %3 limit", source["label"], round(decision["fromPeak"]), name)
|
||||
if decision["action"] == "switch":
|
||||
registry["active"] = target["id"]
|
||||
if not target.get("primary"):
|
||||
link_shared(provider, account_home(provider, target))
|
||||
notify(headline, f"New {name} sessions now use {target['label']} ({round(decision['toPeak'])}%). Running sessions stay on {source['label']}.")
|
||||
notify(headline, text("New %1 sessions now use %2 (%3%). Running sessions stay on %4.", name, target["label"], round(decision["toPeak"]), source["label"]))
|
||||
return True
|
||||
notify(
|
||||
headline,
|
||||
f"{target['label']} is at {round(decision['toPeak'])}%. Click to switch new sessions to it.",
|
||||
text("%1 is at %2%. Click to switch new sessions to it.", target["label"], round(decision["toPeak"])),
|
||||
["omarchy-agent-account-use", provider, target["id"]],
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -31,9 +31,9 @@ active=$(jq -r '.active.label + (if (.active.plan // "") != "" then " (" + .acti
|
||||
if [[ $(jq -r .previous.id <<<"$result") == "$(jq -r .active.id <<<"$result")" ]]; then
|
||||
echo "New $name sessions already use $active."
|
||||
else
|
||||
message="Running sessions stay on $previous."
|
||||
message=$(omarchy-text "Running sessions stay on %1." "$previous")
|
||||
echo "New $name sessions now use $active. $message"
|
||||
omarchy-notification-send -g "New $name sessions now use $active" "$message"
|
||||
omarchy-notification-send -g "$(omarchy-text "New %1 sessions now use %2" "$name" "$active")" "$message"
|
||||
# The usage record's own limits describe the active account.
|
||||
omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 &
|
||||
fi
|
||||
@@ -22,6 +22,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
@@ -32,6 +33,11 @@ from typing import Any
|
||||
|
||||
AGENT_ID = "claude"
|
||||
AGENT_NAME = "Claude Code"
|
||||
def text(english, *args):
|
||||
"""English with %1.. filled, in the session's language, from the shell's table."""
|
||||
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
|
||||
|
||||
|
||||
AUTH_HELP = "Run `claude auth login` to restore authoritative usage."
|
||||
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
|
||||
PROBE_MIN_INTERVAL_SECONDS = 15
|
||||
@@ -989,11 +995,12 @@ def probe_limits(access_token: str) -> dict[str, Any]:
|
||||
except urllib.error.HTTPError as error:
|
||||
retry_after = error.headers.get("retry-after", "") if error.headers else ""
|
||||
if error.code == 429:
|
||||
help_text = "Anthropic's usage endpoint is rate limiting checks right now" + (
|
||||
f" (retry after {retry_after}s)" if retry_after else ""
|
||||
) + ". Local Claude Code stats are still shown."
|
||||
if retry_after:
|
||||
help_text = text("Anthropic's usage endpoint is rate limiting checks right now (retry after %1s). Local Claude Code stats are still shown.", retry_after)
|
||||
else:
|
||||
help_text = text("Anthropic's usage endpoint is rate limiting checks right now. Local Claude Code stats are still shown.")
|
||||
else:
|
||||
help_text = f"Anthropic's usage endpoint returned status {error.code}. Local Claude Code stats are still shown."
|
||||
help_text = text("Anthropic's usage endpoint returned status %1. Local Claude Code stats are still shown.", error.code)
|
||||
return {"ok": False, "helpText": help_text}
|
||||
except Exception:
|
||||
# A transport failure reached no server at all — no route, no DNS. Any
|
||||
|
||||
@@ -13,5 +13,5 @@ fi
|
||||
|
||||
level=$1
|
||||
|
||||
omarchy-notification-send -g -u critical "Time to recharge!" "Battery is down to ${level}%" -i battery-caution -t 30000
|
||||
omarchy-notification-send -g -u critical "Time to recharge!" "$(omarchy-text "Battery is down to %1%" "$level")" -i battery-caution -t 30000
|
||||
omarchy-hook battery-low "$level"
|
||||
@@ -129,9 +129,9 @@ if [[ $charge_holding == "true" ]]; then
|
||||
threshold_label="${threshold_end}%"
|
||||
fi
|
||||
|
||||
echo "Battery ${percentage}% · Holding at ${threshold_label} · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · Holding at %2 · %3W / %4Wh" "$percentage" "$threshold_label" "$power_rate" "$capacity"
|
||||
elif [[ $state == "charging" ]]; then
|
||||
echo "Battery ${percentage}% · ${time_remaining} to full · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · %2 to full · %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
|
||||
else
|
||||
echo "Battery ${percentage}% · ${time_remaining} left · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · %2 left · %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
|
||||
fi
|
||||
@@ -23,7 +23,7 @@
|
||||
OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}"
|
||||
|
||||
if [[ ! -d $OUTPUT_DIR ]]; then
|
||||
omarchy-notification-send -u critical -t 3000 "Screen recording directory does not exist: $OUTPUT_DIR"
|
||||
omarchy-notification-send -u critical -t 3000 "$(omarchy-text "Screen recording directory does not exist: %1" "$OUTPUT_DIR")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ announce() {
|
||||
omarchy-notification-send \
|
||||
--urgency critical \
|
||||
--glyph "$CRASH_GLYPH" \
|
||||
"Process crashed: $comm" \
|
||||
"$(omarchy-text "Process crashed: %1" "$comm")" \
|
||||
"Click to diagnose with AI" \
|
||||
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ fi
|
||||
|
||||
env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default browser"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default browser" "$name")"
|
||||
@@ -59,4 +59,4 @@ fi
|
||||
mkdir -p "$(dirname "$editor_file")"
|
||||
printf '%s\n' "$editor" >"$editor_file"
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default editor"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default editor" "$name")"
|
||||
@@ -48,4 +48,4 @@ cat >~/.config/xdg-terminals.list <<EOF
|
||||
$desktop_id
|
||||
EOF
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default terminal"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default terminal" "$name")"
|
||||
@@ -35,7 +35,7 @@ on() {
|
||||
|
||||
if omarchy-hyprland-toggle-disabled $TOGGLE; then
|
||||
printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG"
|
||||
omarchy-notification-send -g "Mirroring enabled ($EXTERNAL)"
|
||||
omarchy-notification-send -g "$(omarchy-text "Mirroring enabled (%1)" "$EXTERNAL")"
|
||||
hyprctl reload
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ notify_missing_width() {
|
||||
local key="$1"
|
||||
local workspace="$2"
|
||||
|
||||
omarchy-notification-send -g "No saved width found for $key on workspace $workspace" "Use Super + Alt + Home to save one for this workspace."
|
||||
omarchy-notification-send -g "$(omarchy-text "No saved width found for %1 on workspace %2" "$key" "$workspace")" "Use Super + Alt + Home to save one for this workspace."
|
||||
}
|
||||
|
||||
notify_saved_width() {
|
||||
local key="$1"
|
||||
local workspace="$2"
|
||||
|
||||
omarchy-notification-send -g "Saved width for $key on workspace $workspace" "Restore using Super + Home on this workspace."
|
||||
omarchy-notification-send -g "$(omarchy-text "Saved width for %1 on workspace %2" "$key" "$workspace")" "Restore using Super + Home on this workspace."
|
||||
}
|
||||
|
||||
hypr_dispatch() {
|
||||
|
||||
@@ -18,4 +18,4 @@ printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKS
|
||||
|
||||
hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \
|
||||
hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
|
||||
omarchy-notification-send -g "Workspace layout set to $NEW_LAYOUT"
|
||||
omarchy-notification-send -g "$(omarchy-text "Workspace layout set to %1" "$NEW_LAYOUT")"
|
||||
@@ -28,7 +28,7 @@ rows=$(jq -r --arg icon "$PLUGIN_ICON" \
|
||||
". as \$plugins
|
||||
| .[] | select($filter)
|
||||
| \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins")
|
||||
[[ -n $rows ]] || { omarchy-notification-send "No plugin to ${1}"; exit 0; }
|
||||
[[ -n $rows ]] || { omarchy-notification-send "$(omarchy-text "No plugin to $1")"; exit 0; }
|
||||
|
||||
selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0
|
||||
[[ -n $selection ]] || exit 0
|
||||
|
||||
@@ -9,4 +9,4 @@ set -e
|
||||
timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1
|
||||
sudo timedatectl set-timezone "$timezone"
|
||||
omarchy-shell -q omarchy.clock refresh
|
||||
omarchy-notification-send "Timezone is now set to $timezone"
|
||||
omarchy-notification-send "$(omarchy-text "Timezone is now set to %1" "$timezone")"
|
||||
@@ -21,9 +21,9 @@ pending_migrations=$(omarchy-migrate --pending 2>/dev/null) || exit 0
|
||||
pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l)
|
||||
|
||||
if (( pending_count == 1 )); then
|
||||
message="Click to run 1 pending migration."
|
||||
message=$(omarchy-text "Click to run 1 pending migration.")
|
||||
else
|
||||
message="Click to run $pending_count pending migrations."
|
||||
message=$(omarchy-text "Click to run %1 pending migrations." "$pending_count")
|
||||
fi
|
||||
|
||||
# This runs from omarchy-migrate-notify.service after graphical-session.target,
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
# omarchy:summary=Show the current time and date notification
|
||||
|
||||
omarchy-notification-send -g -u low "$(date +"%A %H:%M · %d %B %Y · Week %V")"
|
||||
omarchy-notification-send -g -u low "$(date +"%A %H:%M · %d %B %Y") · $(omarchy-text "Week %1" "$(date +%V)")"
|
||||
@@ -59,7 +59,7 @@ show_reminders() {
|
||||
if [[ -n $reminder_message ]]; then
|
||||
body+="$reminder_message in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
|
||||
else
|
||||
body+="${reminder_minutes}-min reminder in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
|
||||
body+="$(omarchy-text "%1-min reminder in %2 (%3)" "$reminder_minutes" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n'
|
||||
fi
|
||||
done < <(active_reminder_timers "$now")
|
||||
|
||||
@@ -91,7 +91,7 @@ show_json() {
|
||||
if [[ -n $reminder_message ]]; then
|
||||
label=$reminder_message
|
||||
else
|
||||
label="${reminder_minutes}-min reminder"
|
||||
label=$(omarchy-text "%1-min reminder" "$reminder_minutes")
|
||||
fi
|
||||
|
||||
item_json=$(jq -cn \
|
||||
@@ -176,7 +176,7 @@ if [[ -z $minutes ]] || [[ ! $minutes =~ ^[0-9]+$ ]] || ((minutes == 0)); then
|
||||
fi
|
||||
|
||||
if [[ -z $message ]]; then
|
||||
message="Your ${minutes} minutes are up"
|
||||
message=$(omarchy-text "Your %1 minutes are up" "$minutes")
|
||||
fi
|
||||
|
||||
set_at=$(date +%s)
|
||||
@@ -184,14 +184,14 @@ remind_at=$(date -d "+${minutes} minutes" +%H:%M)
|
||||
unit="omarchy-reminder-${minutes}m-$set_at"
|
||||
reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
|
||||
message_file="$reminder_dir/$unit.message"
|
||||
confirmation="You'll be reminded at $remind_at"
|
||||
confirmation_title="Reminder set for ${minutes} minutes"
|
||||
confirmation=$(omarchy-text "You'll be reminded at %1" "$remind_at")
|
||||
confirmation_title=$(omarchy-text "Reminder set for %1 minutes" "$minutes")
|
||||
|
||||
mkdir -p "$reminder_dir"
|
||||
|
||||
if [[ -n $custom_message ]]; then
|
||||
printf "%s" "$custom_message" >"$message_file"
|
||||
confirmation_title="$custom_message in ${minutes} minutes"
|
||||
confirmation_title=$(omarchy-text "%1 in %2 minutes" "$custom_message" "$minutes")
|
||||
fi
|
||||
|
||||
systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \
|
||||
|
||||
@@ -99,7 +99,7 @@ report_unsecured() {
|
||||
|
||||
omarchy-notification-send -u critical -g \
|
||||
"Screen did not lock before suspend" \
|
||||
"The session was left unlocked ($1)." >/dev/null 2>&1 || true
|
||||
"$(omarchy-text "The session was left unlocked (%1)." "$1")" >/dev/null 2>&1 || true
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ claim_path() {
|
||||
announce() {
|
||||
local path="$1"
|
||||
local name="${path##*/}"
|
||||
local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical)
|
||||
local args=("$(omarchy-text "Received %1" "$name")" "$(omarchy-text "Saved to %1" "${dir/#$HOME/~}")" -u critical)
|
||||
|
||||
case "${name,,}" in
|
||||
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
|
||||
|
||||
@@ -27,7 +27,7 @@ if ((${#files[@]} == 0)); then
|
||||
picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$?
|
||||
|
||||
if ((${status:-0} > 1)); then
|
||||
omarchy-notification-send -g "" -u critical "Could not send to $name" \
|
||||
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not send to %1" "$name")" \
|
||||
"The file chooser did not open"
|
||||
exit 1
|
||||
fi
|
||||
@@ -43,8 +43,8 @@ else
|
||||
fi
|
||||
|
||||
if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then
|
||||
omarchy-notification-send -g "" "Sent to $name" "$what"
|
||||
omarchy-notification-send -g "" "$(omarchy-text "Sent to %1" "$name")" "$what"
|
||||
else
|
||||
omarchy-notification-send -g "" -u critical "Could not send to $name" "${error:-Taildrop transfer failed}"
|
||||
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not send to %1" "$name")" "${error:-$(omarchy-text "Taildrop transfer failed")}"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Print an Omarchy message in the session's language, with %1.. filled from the arguments
|
||||
# omarchy:args=<english> [args...]
|
||||
# omarchy:examples=omarchy-text "Sent to %1" phone
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (($# < 1)); then
|
||||
echo "Usage: omarchy-text <english> [args...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
text=$1
|
||||
shift
|
||||
|
||||
# The shell's table is the one source: one JSON-encoded "english": "chinese" pair per line.
|
||||
if [[ ${LANG:-} == zh_CN* ]]; then
|
||||
translated=$(grep -E '^ ".*": ".*",?$' "$OMARCHY_PATH/shell/Commons/I18nZh.js" | sed 's/,$//' | paste -sd, |
|
||||
sed 's/^/{/; s/$/}/' | jq -r --arg key "$text" '.[$key] // empty')
|
||||
[[ -z $translated ]] || text=$translated
|
||||
fi
|
||||
|
||||
# One pass over the template, so an argument that holds %2 is printed as given.
|
||||
out=""
|
||||
rest=$text
|
||||
while [[ $rest =~ ^([^%]*)%(.)(.*)$ ]]; do
|
||||
out+=${BASH_REMATCH[1]}
|
||||
mark=${BASH_REMATCH[2]}
|
||||
rest=${BASH_REMATCH[3]}
|
||||
if [[ $mark == [1-9] ]] && ((mark <= $#)); then
|
||||
out+=${!mark}
|
||||
else
|
||||
out+="%$mark"
|
||||
fi
|
||||
done
|
||||
printf '%s\n' "$out$rest"
|
||||
@@ -193,14 +193,14 @@ main() {
|
||||
output=$(output_path "$input" "$format" "$resolution")
|
||||
|
||||
if [[ $type == "video" ]]; then
|
||||
omarchy-notification-send -g "Transcoding video…" "$(basename -- "$input") to $format ($resolution)"
|
||||
omarchy-notification-send -g "Transcoding video…" "$(omarchy-text "%1 to %2 (%3)" "$(basename -- "$input")" "$format" "$resolution")"
|
||||
transcode_video "$input" "$format" "$resolution" "$output"
|
||||
copy_to_clipboard "$output"
|
||||
omarchy-notification-send -g "Transcoded to $resolution $format" "Saved and copied to clipboard."
|
||||
omarchy-notification-send -g "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
|
||||
else
|
||||
transcode_picture "$input" "$format" "$resolution" "$output"
|
||||
copy_to_clipboard "$output"
|
||||
omarchy-notification-send -g "Transcoded to $resolution $format" "Saved and copied to clipboard."
|
||||
omarchy-notification-send -g "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,4 @@ QtObject {
|
||||
function trMatch(text, templates) {
|
||||
return Tr.trMatch(text, templates)
|
||||
}
|
||||
|
||||
function trMessage(text) {
|
||||
return Tr.trMessage(text)
|
||||
}
|
||||
}
|
||||
+23
-54
@@ -480,7 +480,6 @@ var table = {
|
||||
"Couldn't add the %1 account": "无法添加 %1 账户",
|
||||
"Mirroring enabled (%1)": "已开启镜像(%1)",
|
||||
"New %1 sessions now use %2": "新的 %1 会话现在使用 %2",
|
||||
"No plugin to %1": "没有插件可以%1",
|
||||
"No saved width found for %1 on workspace %2": "工作区 %2 没有为 %1 保存的宽度",
|
||||
"Saved width for %1 on workspace %2": "已为工作区 %2 的 %1 保存宽度",
|
||||
"Screen recording directory does not exist: %1": "录屏目录不存在:%1",
|
||||
@@ -528,9 +527,29 @@ var table = {
|
||||
"The session was left unlocked (%1).": "会话没有锁定(%1)。",
|
||||
"%1 to %2 (%3)": "%1 转为 %2(%3)",
|
||||
"Battery %1% · Holding at %2 · %3W / %4Wh": "电池 %1% · 保持在 %2 · %3W / %4Wh",
|
||||
"Battery %1% · %2 to full · %3W / %4Wh": "电池 %1% · %2 后充满 · %3W / %4Wh",
|
||||
"Battery %1% · %2 left · %3W / %4Wh": "电池 %1% · 剩余 %2 · %3W / %4Wh",
|
||||
"%1 · %2 · Week %3": "%1 · %2 · 第 %3 周",
|
||||
"Battery %1% · %2 to full · %3W / %4Wh": "电池 %1% · %2 后充满 · %3W / %4Wh",
|
||||
"Battery %1% · %2 left · %3W / %4Wh": "电池 %1% · 剩余 %2 · %3W / %4Wh",
|
||||
"Week %1": "第 %1 周",
|
||||
"Installing the %1 CLI didn't finish.": "%1 命令行没有装完。",
|
||||
"No %1 account named %2.": "没有名为 %2 的 %1 账户。",
|
||||
"No %1 account named %2": "没有名为 %2 的 %1 账户",
|
||||
"Couldn't set up a home for the new account.": "无法为新账户准备目录。",
|
||||
"%1 is not a pending %2 login": "%1 不是待完成的 %2 登录",
|
||||
"The login didn't finish, so no account was added.": "登录没有完成,没有添加账户。",
|
||||
"That's %1 (%2). Try again, and sign in as the other account in the private window.": "这是 %1(%2)。请重试,并在隐私窗口中登录另一个账户。",
|
||||
"already added": "已添加",
|
||||
"Give the account a name": "给账户起个名字",
|
||||
"%1 is the login in %2; it can't be removed here.": "%1 是 %2 中的登录,不能在这里移除。",
|
||||
"A running %1 session uses %2. Quit it first, then remove the account.": "有一个正在运行的 %1 会话使用 %2。先退出它,再移除账户。",
|
||||
"Switch mode is manual or auto": "切换模式为手动或自动",
|
||||
"The threshold is a percentage between 50 and 100": "阈值是 50 到 100 之间的百分比",
|
||||
"%1-min reminder in %2 (%3)": "%1 分钟提醒,还有 %2(%3)",
|
||||
"%1-min reminder": "%1 分钟提醒",
|
||||
"Your %1 minutes are up": "%1 分钟到了",
|
||||
"No plugin to enable": "没有可启用的插件",
|
||||
"No plugin to disable": "没有可停用的插件",
|
||||
"No plugin to clone": "没有可克隆的插件",
|
||||
"No plugin to remove": "没有可移除的插件",
|
||||
// Clock
|
||||
"Right-click to toggle format": "右键切换格式",
|
||||
"Back to today": "回到今天",
|
||||
@@ -578,53 +597,3 @@ var table = {
|
||||
"Next": "下一首",
|
||||
"Previous": "上一首"
|
||||
}
|
||||
|
||||
// Parameterized messages that arrive finished; trMessage matches them, so each needs a table entry above.
|
||||
var templates = [
|
||||
"Anthropic's usage endpoint is rate limiting checks right now (retry after %1s). Local Claude Code stats are still shown.",
|
||||
"Anthropic's usage endpoint returned status %1. Local Claude Code stats are still shown.",
|
||||
"New %1 sessions now use %2 (%3%). Running sessions stay on %4.",
|
||||
"Sign in to %1 again in the browser window that opens.",
|
||||
"%1 is at %2%. Click to switch new sessions to it.",
|
||||
"Sign in to %1 in the browser window that opens.",
|
||||
"Screen recording directory does not exist: %1",
|
||||
"Battery %1% · Holding at %2 · %3W / %4Wh",
|
||||
"No saved width found for %1 on workspace %2",
|
||||
"Battery %1% · %2 to full · %3W / %4Wh",
|
||||
"Battery %1% · %2 left · %3W / %4Wh",
|
||||
"Click to run %1 pending migrations.",
|
||||
"The session was left unlocked (%1).",
|
||||
"Saved width for %1 on workspace %2",
|
||||
"Staying on %1. %2 resets in %3.",
|
||||
"%1 is now the default terminal",
|
||||
"%1 is now the default browser",
|
||||
"The %1 sign-in didn't finish.",
|
||||
"%1 is now the default editor",
|
||||
"All %1 accounts are over %2%",
|
||||
"Running sessions stay on %1.",
|
||||
"Couldn't add the %1 account",
|
||||
"Reminder set for %1 minutes",
|
||||
"New %1 sessions now use %2",
|
||||
"Workspace layout set to %1",
|
||||
"%1 is at %2% of a %3 limit",
|
||||
"Timezone is now set to %1",
|
||||
"You'll be reminded at %1",
|
||||
"Battery is down to %1%",
|
||||
"Mirroring enabled (%1)",
|
||||
"Installing the %1 CLI…",
|
||||
"Signed in to %1 again.",
|
||||
"%1 · %2 · Week %3",
|
||||
"Could not send to %1",
|
||||
"Transcoded to %1 %2",
|
||||
"Process crashed: %1",
|
||||
"Signed in to %1.",
|
||||
"%1 in %2 minutes",
|
||||
"No plugin to %1",
|
||||
"Staying on %1.",
|
||||
"%1 to %2 (%3)",
|
||||
"%1 installed",
|
||||
"Received %1",
|
||||
"Saved to %1",
|
||||
"Sent to %1",
|
||||
"Added %1."
|
||||
]
|
||||
+1
-6
@@ -13,7 +13,7 @@ function tr(text) {
|
||||
})
|
||||
}
|
||||
|
||||
// Text built elsewhere around a name: the first English template that matches is translated with what %1.. captured.
|
||||
// Text a panel's own model builds in known formats: the first matching template is translated with what %1.. captured.
|
||||
function trMatch(text, templates) {
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
var pattern = "^" + templates[i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%\d/g, "(.+)") + "$"
|
||||
@@ -22,8 +22,3 @@ function trMatch(text, templates) {
|
||||
}
|
||||
return tr(text)
|
||||
}
|
||||
|
||||
// Finished messages from other programs (notifications, collectors) that carry a name or number.
|
||||
function trMessage(text) {
|
||||
return trMatch(text, Zh.templates)
|
||||
}
|
||||
@@ -1173,7 +1173,7 @@ Panel {
|
||||
Text {
|
||||
width: parent.width
|
||||
textFormat: Text.PlainText
|
||||
text: I18n.trMessage(root.addStatus)
|
||||
text: I18n.tr(root.addStatus)
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
@@ -1243,7 +1243,7 @@ Panel {
|
||||
visible: root.addStage === "done" || root.addStage === "error"
|
||||
width: parent.width
|
||||
textFormat: Text.PlainText
|
||||
text: I18n.trMessage(root.addResult)
|
||||
text: I18n.tr(root.addResult)
|
||||
color: root.addStage === "error" ? root.urgent : root.foreground
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
@@ -1350,7 +1350,7 @@ Panel {
|
||||
width: parent.width
|
||||
textFormat: Text.PlainText
|
||||
// Shown for the status, so a record with no help to offer says the status rather than nothing.
|
||||
text: section.provider ? I18n.trMessage(String(section.provider.authHelpText || section.provider.usageStatusText || "")) : ""
|
||||
text: section.provider ? I18n.tr(String(section.provider.authHelpText || section.provider.usageStatusText || "")) : ""
|
||||
color: root.urgent
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
@@ -1879,7 +1879,7 @@ Panel {
|
||||
|
||||
PanelToolTip {
|
||||
visible: unavailableHover.hovered && unavailable.visible
|
||||
text: unavailable.record ? I18n.trMessage(String(unavailable.record.authHelpText || "Usage has not been updated yet.")) : ""
|
||||
text: unavailable.record ? I18n.tr(String(unavailable.record.authHelpText || "Usage has not been updated yet.")) : ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ BorderSurface {
|
||||
readonly property bool singleLineToast: sanitizedBody.length === 0
|
||||
readonly property bool collapseRedundantIcon: singleLineToast && !hasGlyph && summaryStartsWithGlyph
|
||||
readonly property string sanitizedBody: sanitizeBody(body)
|
||||
readonly property string styledBody: NotificationLogic.styledBody(I18n.trMessage(body), app, appIcon)
|
||||
readonly property string styledBody: NotificationLogic.styledBody(I18n.tr(body), app, appIcon)
|
||||
|
||||
readonly property color dimColor: Qt.darker(Color.notifications.text, 1.4)
|
||||
readonly property color bodyColor: Qt.darker(Color.notifications.text, 1.15)
|
||||
@@ -168,7 +168,7 @@ BorderSurface {
|
||||
textFormat: Text.PlainText
|
||||
Layout.fillWidth: true
|
||||
visible: root.summary.length > 0
|
||||
text: I18n.trMessage(root.summary)
|
||||
text: I18n.tr(root.summary)
|
||||
font.family: "Liberation Sans"
|
||||
color: Color.notifications.text
|
||||
font.pixelSize: Style.font.title
|
||||
|
||||
@@ -90,7 +90,7 @@ Item {
|
||||
font.family: Style.font.family
|
||||
font.bold: true
|
||||
font.pixelSize: Style.font.title
|
||||
text: I18n.trMessage(root.message)
|
||||
text: I18n.tr(root.message)
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
@@ -190,7 +190,7 @@ Item {
|
||||
// a hole in the padding; the slack lands in the gap after the bar.
|
||||
horizontalAlignment: root.hasProgress ? Text.AlignRight : Text.AlignLeft
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.trMessage(root.message)
|
||||
text: I18n.tr(root.message)
|
||||
font: messageMetrics.font
|
||||
color: Color.popups.text
|
||||
elide: Text.ElideRight
|
||||
|
||||
@@ -291,7 +291,7 @@ Panel {
|
||||
textFormat: Text.PlainText
|
||||
visible: dropbox.actionStatus !== "" || dropbox.lastError !== ""
|
||||
width: parent.width
|
||||
text: I18n.trMessage(dropbox.actionStatus !== "" ? dropbox.actionStatus : dropbox.lastError)
|
||||
text: I18n.tr(dropbox.actionStatus !== "" ? dropbox.actionStatus : dropbox.lastError)
|
||||
color: dropbox.lastError !== "" && dropbox.actionStatus === "" ? root.urgent : root.dim
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
|
||||
@@ -5,6 +5,12 @@ source "$(dirname "$0")/base-test.sh"
|
||||
require_command jq
|
||||
require_command python3
|
||||
|
||||
# The collector words its diagnostics through omarchy-text.
|
||||
text_bin=$(mktemp -d)
|
||||
trap 'rm -rf "$text_bin"' EXIT
|
||||
ln -s "$ROOT/bin/omarchy-text" "$text_bin/omarchy-text"
|
||||
export PATH="$text_bin:$PATH"
|
||||
|
||||
# probe_limits reaches Anthropic, so the reader that interprets its answer is
|
||||
# exercised on its own: the collector loads as a module, and a recorded payload
|
||||
# stands in for the response.
|
||||
@@ -112,7 +118,7 @@ pass "Claude collector keeps Max multipliers and plain plan names"
|
||||
# is still good. Drive collect_limits over a planted cache with the network
|
||||
# unreachable, so nothing but the credential state decides the answer.
|
||||
CACHE_HOME=$(mktemp -d)
|
||||
trap 'rm -rf "$CACHE_HOME"' EXIT
|
||||
trap 'rm -rf "$CACHE_HOME" "$text_bin"' EXIT
|
||||
|
||||
collect_limits() {
|
||||
COLLECTOR="$ROOT/bin/omarchy-agent-usage-claude" TOKEN="$1" EXPIRES_AT="$2" \
|
||||
|
||||
@@ -31,7 +31,7 @@ assert(!/opacity: stale \?/.test(panelSource) && /I18n\.tr\("Last updated %1 ago
|
||||
assert(/onPickingChanged: resetKeys\(\)/.test(panelSource), 'the cursor starts over when the agent list comes or goes')
|
||||
assert(!/t === "a" \|\| t === "A"/.test(panelSource), 'adding an account has no hotkey; the + is the way in')
|
||||
assert(/if \(!accounts\[a\]\.active\) \{/.test(panelSource) && /if \(row\.length > 0\) rows\.push\(row\)/.test(panelSource), 'the active account with nothing to fix is not a keyboard stop')
|
||||
assert(/text: section\.provider \? I18n\.trMessage\(String\(section\.provider\.authHelpText \|\| section\.provider\.usageStatusText \|\| ""\)\) : ""/.test(panelSource), 'the trouble line falls back to the usage status when a record has no auth help')
|
||||
assert(/text: section\.provider \? I18n\.tr\(String\(section\.provider\.authHelpText \|\| section\.provider\.usageStatusText \|\| ""\)\) : ""/.test(panelSource), 'the trouble line falls back to the usage status when a record has no auth help')
|
||||
const mainSource = fs.readFileSync(root + '/shell/plugins/agents/Main.qml', 'utf8')
|
||||
assert(/var from = Math\.min\(0\.8, \(threshold - 15\) \/ 100\)/.test(mainSource), 'faster checks start 15 points below the switch threshold')
|
||||
assert(/rows\.push\(\[\{ kind: "provider", index: p \}\]\)/.test(panelSource), "every agent's header is a keyboard stop")
|
||||
|
||||
@@ -11,6 +11,7 @@ trap 'rm -rf "$TMPDIR"' EXIT
|
||||
|
||||
STUB_DIR="$TMPDIR/stub"
|
||||
mkdir -p "$STUB_DIR"
|
||||
ln -s "$ROOT/bin/omarchy-text" "$STUB_DIR/omarchy-text"
|
||||
|
||||
# The picker reads the plugin list from omarchy-plugin-list and hands what it
|
||||
# decided to a verb-specific command, so stubbing both ends shows which plugin
|
||||
|
||||
@@ -137,7 +137,7 @@ assertEqual(
|
||||
// belongs in the logic module, where the strip runs after it.
|
||||
const cardQml = fs.readFileSync(path.join(root, 'shell/plugins/notifications/components/NotificationCard.qml'), 'utf8')
|
||||
assert(
|
||||
/readonly property string styledBody: NotificationLogic\.styledBody\(I18n\.trMessage\(body\), app, appIcon\)/.test(cardQml),
|
||||
/readonly property string styledBody: NotificationLogic\.styledBody\(I18n\.tr\(body\), app, appIcon\)/.test(cardQml),
|
||||
'the notification card renders the body that was stripped after the newline rewrite'
|
||||
)
|
||||
assert(
|
||||
|
||||
@@ -10,7 +10,7 @@ const vm = require('vm')
|
||||
|
||||
const source = fs.readFileSync(path.join(root, 'shell/Commons/I18nZh.js'), 'utf8').replace('.pragma library', '')
|
||||
const context = {}
|
||||
vm.runInNewContext(source + '\nthis.table = table; this.templates = templates', context)
|
||||
vm.runInNewContext(source + '\nthis.table = table', context)
|
||||
const table = context.table
|
||||
|
||||
const files = []
|
||||
@@ -40,11 +40,33 @@ for (const file of files) {
|
||||
}
|
||||
}
|
||||
assert(calls > 300, 'the shell routes its text through I18n.tr', `found ${calls} calls`)
|
||||
const missingTemplates = Array.from(context.templates).filter(t => !(t in table))
|
||||
assert(missingTemplates.length === 0, 'every message template has a Chinese entry', missingTemplates.join('\n'))
|
||||
|
||||
// Senders translate messages that carry a name or number with omarchy-text, or text() in Python.
|
||||
const senders = []
|
||||
for (const entry of fs.readdirSync(path.join(root, 'bin'), { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue
|
||||
const name = entry.name
|
||||
const text = fs.readFileSync(path.join(root, 'bin', name), 'utf8')
|
||||
for (const match of text.matchAll(/(?:omarchy-text\s+|\btext\(\s*)"((?:[^"\\]|\\.)*)"/g)) {
|
||||
if (match[1].includes('$')) continue
|
||||
senders.push([name, JSON.parse('"' + match[1] + '"')])
|
||||
}
|
||||
}
|
||||
assert(senders.length > 40, 'senders translate their parameterized messages', `found ${senders.length}`)
|
||||
const unsent = senders.filter(([, key]) => !(key in table)).map(([file, key]) => `bin/${file}: ${key}`)
|
||||
assert(unsent.length === 0, 'every sender message has a Chinese entry', unsent.join('\n'))
|
||||
assert(missing.length === 0, 'every I18n.tr text has a Chinese entry', missing.join('\n'))
|
||||
|
||||
const placeholders = s => (s.match(/%\d/g) || []).sort().join(',')
|
||||
const mismatched = Object.keys(table).filter(key => placeholders(key) !== placeholders(table[key]))
|
||||
assert(mismatched.length === 0, 'every Chinese entry keeps its English placeholders', mismatched.join('\n'))
|
||||
JS
|
||||
|
||||
text=$ROOT/bin/omarchy-text
|
||||
[[ $(OMARCHY_PATH=$ROOT LANG=zh_CN.UTF-8 "$text" "Sent to %1" "phone %2") == "已发送给 phone %2" ]] ||
|
||||
fail "omarchy-text translates and fills %1 in one pass"
|
||||
[[ $(OMARCHY_PATH=$ROOT LANG=en_US.UTF-8 "$text" "Battery is down to %1%" 9) == "Battery is down to 9%" ]] ||
|
||||
fail "omarchy-text keeps English and a literal % outside zh_CN"
|
||||
[[ $(OMARCHY_PATH=$ROOT LANG=zh_CN.UTF-8 "$text" "Unknown 50% of %1" x) == "Unknown 50% of x" ]] ||
|
||||
fail "omarchy-text passes an unknown message through"
|
||||
pass "omarchy-text translates by exact key and fills arguments in one pass"
|
||||
@@ -10,6 +10,7 @@ trap cleanup EXIT
|
||||
|
||||
downloads="$WORKDIR/downloads"
|
||||
mkdir -p "$WORKDIR/bin" "$downloads" "$WORKDIR/outbox"
|
||||
ln -s "$ROOT/bin/omarchy-text" "$WORKDIR/bin/omarchy-text"
|
||||
printf 'mine' >"$downloads/unrelated.txt"
|
||||
|
||||
# Stands in for the daemon handing over whatever is waiting in the inbox. A
|
||||
|
||||
Reference in new issue
Block a user