#!/usr/bin/env bash # force-english-all-users.sh — pin Configuration.UICulture=en-US on every Jellyfin user. # # Why this exists: see docs/15-force-english.md. # TL;DR — when a user has UICulture unset, the Jellyfin web SPA falls back to # browser Accept-Language. Owner saw "Abspielen" (German "Play") on a Play # button because someone's browser sends de-*. Pinning UICulture per user # overrides Accept-Language and gives every account English UI regardless # of where they log in from. # # Read-modify-write on /Users/{id}/Configuration. Idempotent — running it # twice produces the same end state. Prints before/after UICulture per user. # # Usage: # JELLYFIN_TOKEN= ./force-english-all-users.sh # # Optional env: # JELLYFIN_URL default https://arrflix.s8n.ru # TARGET_LOCALE default en-US (e.g. en-GB also works) # DRY_RUN default unset; set DRY_RUN=1 to print payloads without POSTing set -euo pipefail JELLYFIN_URL="${JELLYFIN_URL:-https://arrflix.s8n.ru}" JELLYFIN_TOKEN="${JELLYFIN_TOKEN:?set JELLYFIN_TOKEN=}" TARGET_LOCALE="${TARGET_LOCALE:-en-US}" DRY_RUN="${DRY_RUN:-}" AUTH="MediaBrowser Token=$JELLYFIN_TOKEN" echo "[*] Listing users..." USERS_JSON=$(curl -ks "$JELLYFIN_URL/Users" -H "Authorization: $AUTH") COUNT=$(echo "$USERS_JSON" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))") echo " $COUNT users found." echo # Iterate. Pipe-to-while loses set -e on subshell exit, so use process-substitution. while IFS=$'\t' read -r USER_ID USER_NAME OLD_CULTURE; do echo "[*] $USER_NAME ($USER_ID)" echo " before: UICulture=${OLD_CULTURE:-}" if [[ "$OLD_CULTURE" == "$TARGET_LOCALE" ]]; then echo " skip: already $TARGET_LOCALE" echo continue fi TMP_IN=$(mktemp) TMP_OUT=$(mktemp) curl -ks "$JELLYFIN_URL/Users/$USER_ID" -H "Authorization: $AUTH" > "$TMP_IN" python3 - < "$TMP_OUT" import json with open("$TMP_IN") as f: u = json.load(f) c = u["Configuration"] c["UICulture"] = "$TARGET_LOCALE" print(json.dumps(c)) PYEOF if [[ -n "$DRY_RUN" ]]; then echo " DRY_RUN: would POST $(wc -c < "$TMP_OUT") bytes to /Users/$USER_ID/Configuration" else HTTP=$(curl -ks -X POST "$JELLYFIN_URL/Users/$USER_ID/Configuration" \ -H "Authorization: $AUTH" \ -H "Content-Type: application/json" \ --data-binary @"$TMP_OUT" -w "%{http_code}" -o /dev/null) if [[ "$HTTP" != "204" ]]; then echo " ERROR: POST returned HTTP $HTTP" rm -f "$TMP_IN" "$TMP_OUT" exit 1 fi # Verify NEW_CULTURE=$(curl -ks "$JELLYFIN_URL/Users/$USER_ID" -H "Authorization: $AUTH" \ | python3 -c "import json,sys; print(json.load(sys.stdin)['Configuration'].get('UICulture',''))") echo " after: UICulture=$NEW_CULTURE" fi rm -f "$TMP_IN" "$TMP_OUT" echo done < <(echo "$USERS_JSON" | python3 -c " import json, sys for u in json.load(sys.stdin): cur = u.get('Configuration', {}).get('UICulture', '') print(f\"{u['Id']}\t{u['Name']}\t{cur}\") ") echo "[*] Done. Tell users to hard-refresh (Ctrl-Shift-R) so the SPA reloads" echo " the locale bundle. Verify on a movie detail page — Play button" echo " should read 'Play', not 'Abspielen'."