Finish the wonderland: System told truthfully, in eight tabs instead of ten

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-24 23:31:52 -04:00
parent 9ffaf45a4d
commit be0e55214b
57 changed files with 5040 additions and 925 deletions
+128 -12
View File
@@ -2,9 +2,25 @@
# System locale, via localectl.
#
# panama-locale list -> [{value, label, detail}]
# panama-locale get -> the current LANG, e.g. en_US.UTF-8
# panama-locale list -> [{value, label, detail}]
# panama-locale get -> the current LANG, e.g. en_US.UTF-8
# panama-locale set <locale>
# panama-locale categories -> the category names, one per line
# panama-locale overrides -> {LC_TIME: "...", ...}, "" for none
# panama-locale get <category> -> the override, or "" for "match language"
# panama-locale set <category> <locale|"">
#
# The categories are the five that a person actually chooses independently of
# their language: LC_TIME, LC_NUMERIC, LC_MONETARY, LC_MEASUREMENT, LC_PAPER.
# Someone reading in English while writing dates and currency the way their
# country does is the ordinary case, not an exotic one.
#
# An empty value means "match language", which is the ABSENCE of an override
# rather than a value equal to LANG -- the two behave the same today and
# diverge the moment the language changes. localectl replaces /etc/locale.conf
# with exactly the assignments it is given, so unsetting one category means
# re-issuing all the others. That is why cmd_set_category reads the current
# file first: passing only the survivors is the only way to remove one.
#
# Locale codes are not names. "pt_BR.UTF-8" tells you what it means only if you
# already know, which defeats the point of a picker, so codes are resolved
@@ -24,11 +40,63 @@ set -uo pipefail
readonly ISO_LANG=/usr/share/iso-codes/json/iso_639-2.json
readonly ISO_COUNTRY=/usr/share/iso-codes/json/iso_3166-1.json
readonly LOCALE_CONF=/etc/locale.conf
readonly CATEGORIES=(LC_TIME LC_NUMERIC LC_MONETARY LC_MEASUREMENT LC_PAPER)
is_category() {
local candidate="$1" name
for name in "${CATEGORIES[@]}"; do
[[ "$name" == "$candidate" ]] && return 0
done
return 1
}
# LANG plus every category override, as VAR=value lines. /etc/locale.conf is
# what localectl writes and is world-readable, so it is read directly rather
# than scraped out of `localectl status`, whose multi-variable output wraps
# across continuation lines and has no stable machine form.
current_assignments() {
[[ -r "$LOCALE_CONF" ]] || return 0
while IFS= read -r line; do
line="${line%%#*}"
[[ "$line" =~ ^[[:space:]]*(LANG|LANGUAGE|LC_[A-Z_]+)=(.*)$ ]] || continue
local name="${BASH_REMATCH[1]}" value="${BASH_REMATCH[2]}"
# locale.conf quotes values; localectl takes them bare.
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
[[ -n "$value" ]] && printf '%s=%s\n' "$name" "$value"
done <"$LOCALE_CONF"
}
cmd_get() {
localectl status 2>/dev/null \
| awk -F'LANG=' '/System Locale:/ { print $2; exit }' \
| tr -d '[:space:]'
local name="${1:-LANG}"
if [[ "$name" != "LANG" ]] && ! is_category "$name"; then
printf 'panama-locale: %s is not a category this manages\n' "$name" >&2
return 2
fi
local value
value="$(current_assignments | awk -F= -v want="$name" '$1 == want { print $2; exit }')"
# A machine with no /etc/locale.conf still has a LANG, and localectl is the
# one that knows it. Categories have no such fallback: absent there means
# absent, which is exactly "match language".
if [[ -z "$value" && "$name" == "LANG" ]]; then
value="$(localectl status 2>/dev/null \
| awk -F'LANG=' '/System Locale:/ { print $2; exit }' \
| tr -d '[:space:]')"
fi
printf '%s\n' "$value"
}
# Every category override in one answer: {"LC_TIME": "de_DE.UTF-8", ...} with
# "" for the ones that match the language. Five separate `get` calls would be
# five processes for one screenful of state.
cmd_overrides() {
local assignments name
assignments="$(current_assignments)"
{ for name in "${CATEGORIES[@]}"; do
printf '%s\t%s\n' "$name" \
"$(awk -F= -v want="$name" '$1 == want { print $2; exit }' <<<"$assignments")"
done; } | jq -Rn '[inputs | split("\t") | {key: .[0], value: (.[1] // "")}] | from_entries'
}
cmd_list() {
@@ -75,10 +143,11 @@ cmd_list() {
' <<<"$locales"
}
cmd_set() {
local locale="${1:-}"
# Constrained rather than passed through: this reaches a privileged
# command, and the set of legal locale names is narrow and well known.
# A locale name that is both well formed and actually installed. Everything
# reaching localectl goes through this: it is a privileged command, and the set
# of legal locale names is narrow and well known.
installed_locale() {
local locale="$1"
[[ "$locale" =~ ^[[email protected]]+$ ]] || {
printf 'panama-locale: refusing a locale name with unexpected characters\n' >&2
return 2
@@ -87,12 +156,59 @@ cmd_set() {
printf 'panama-locale: %s is not an installed locale\n' "$locale" >&2
return 2
}
}
cmd_set() {
local locale="${1:-}"
installed_locale "$locale" || return 2
# LANG is set on its own rather than through the rewrite path: changing the
# language must not quietly drop category overrides somebody chose, and
# localectl merges a lone LANG= assignment into the existing file.
localectl set-locale "LANG=$locale"
}
cmd_set_category() {
local name="${1:-}" locale="${2:-}"
is_category "$name" || {
printf 'panama-locale: %s is not a category this manages\n' "$name" >&2
return 2
}
local assignments=() line
while IFS= read -r line; do
[[ "${line%%=*}" == "$name" ]] && continue
assignments+=("$line")
done < <(current_assignments)
if [[ -n "$locale" ]]; then
installed_locale "$locale" || return 2
assignments+=("$name=$locale")
fi
# An empty file would leave the system with no LANG at all. Nothing here
# should be able to produce that, but refusing is cheaper than explaining.
(( ${#assignments[@]} > 0 )) || {
printf 'panama-locale: refusing to clear every locale setting\n' >&2
return 2
}
localectl set-locale "${assignments[@]}"
}
case "${1:-list}" in
list) cmd_list ;;
get) cmd_get ;;
set) shift; cmd_set "${1:-}" ;;
*) printf 'usage: panama-locale [list|get|set <locale>]\n' >&2; exit 2 ;;
get) shift; cmd_get "${1:-LANG}" ;;
set)
shift
if is_category "${1:-}"; then
cmd_set_category "${1:-}" "${2:-}"
else
cmd_set "${1:-}"
fi
;;
categories) printf '%s\n' "${CATEGORIES[@]}" ;;
overrides) cmd_overrides ;;
*)
printf 'usage: panama-locale [list|categories|overrides|get [category]|set [category] <locale>]\n' >&2
exit 2
;;
esac