# 15 - Force English UI for All Users > Why "Abspielen" showed up on the Play button, every place locale comes from, > and the per-user mechanism (plus wrapper update) that pins every account > to English regardless of what `Accept-Language` the browser sends. Last verified: 2026-05-08 against Jellyfin 10.10.3 web bundle, arrflix.s8n.ru. --- ## TL;DR - Owner saw German "Abspielen" on the detail-page Play button. - Root cause: **every Jellyfin user on this server has `Configuration.UICulture` unset** (key is absent from `GET /Users/{id}` JSON, not just empty string). When that field is missing, the Jellyfin web SPA falls back to the browser's `Accept-Language` header. A browser sending `de-*` → German UI. - There is **no server-side flag** that forces the web client to ignore `Accept-Language`. Locale is per-user. - Fix: `POST /Users/{id}/Configuration` with `UICulture` pinned to `"en-US"` for every existing user, and update `bin/add-jellyfin-user.sh` so future users get the same pin baked in at creation time. --- ## Where Jellyfin gets UI language from (priority order) The Jellyfin web client (`/web/index.html` SPA) selects its UI language in this exact order, first hit wins: | # | Source | Where it lives | Notes | |---|--------|----------------|-------| | 1 | **Per-user `Configuration.UICulture`** | `GET /Users/{id}` JSON, field `Configuration.UICulture` | Authoritative once a user is logged in. Set to `"en-US"` to pin English. | | 2 | **Browser `Accept-Language`** | HTTP request header, sent by every browser | Fallback when (1) is unset / empty / absent. This is what bit us — Marco's browser sends `de-DE,de;q=0.9,en` and Jellyfin honored it. | | 3 | **Server `UICulture`** in `/System/Configuration` | Server-wide JSON, current value `"en-US"` | This is the **dashboard / admin** default, NOT applied to user UI. Misleading: setting it does NOT propagate down to clients. | | 4 | **Pre-auth splash bundle strings** | Static strings in the JS bundle's `en-us.json`/`de.json` | Loaded based on `Accept-Language` BEFORE the user is even authed. Cannot be overridden per-user — see "Limits" below. | There is **no** `customPrefs.language` key in `DisplayPreferences` — locale is not stored there. Confirmed by inspecting marco's `DisplayPreferences/usersettings`: `CustomPrefs` has only `chromecastVersion`, `dashboardTheme`, home sections, skip lengths, `tvhome`. No language. There is **no** `EnableNonAdministrativeUserLocaleOverride` or `EnforcedDisplayLanguage` flag in `/System/Configuration`. Verified via filtering the full server config for `lang|locale|culture|country` keys — only `PreferredMetadataLanguage`, `MetadataCountryCode`, and `UICulture` exist, and `UICulture` server-side is the dashboard-only default. --- ## Per-user state (current) Audit run 2026-05-08, all 5 users: | User | UserId | `Configuration.UICulture` | |------|--------|---------------------------| | 5 | `571decc67cdc4ea683b4c936b0a31ff8` | **key absent** | | guest | `82dd8542915740c8ae799b6723542c63` | **key absent** | | house | `a4cbcdf95bb34888885af6fbf5c340d1` | **key absent** | | marco | `d787fbfc373a44119a247e7406b2721e` | **key absent** | | s8n | `2be0f0d3fe3a45dc9298138a15a01925` | **key absent** | Every account is currently at the mercy of the browser. Whichever browser hits arrflix.s8n.ru with `Accept-Language: de-*` will see German strings (Play → Abspielen, Resume → Fortsetzen, etc.). The Play button screenshot the owner shared is almost certainly Marco logged in from a German-locale browser, or any user logged in from such a browser at all. --- ## Forcing mechanism — per-user POST The web client reads `UICulture` straight from the user object on auth and on every refresh. Setting it to `"en-US"` pins the UI to English regardless of what the browser asks for. **Endpoint:** `POST /Users/{userId}/Configuration` (returns 204). **Payload:** the FULL existing `Configuration` block with `UICulture` added (Jellyfin replaces the whole config dict, it does not patch fields). Fetch first, modify, POST back — the same read-modify-write pattern step [3/4] of `add-jellyfin-user.sh` already uses. **Reference curl** (single user, marco): ```bash TOKEN=*redacted* USER_ID=d787fbfc373a44119a247e7406b2721e curl -s "https://arrflix.s8n.ru/Users/$USER_ID" \ -H "Authorization: MediaBrowser Token=$TOKEN" > /tmp/u.json python3 -c " import json with open('/tmp/u.json') as f: u = json.load(f) c = u['Configuration'] c['UICulture'] = 'en-US' print(json.dumps(c)) " > /tmp/u-fixed.json curl -s -X POST "https://arrflix.s8n.ru/Users/$USER_ID/Configuration" \ -H "Authorization: MediaBrowser Token=$TOKEN" \ -H "Content-Type: application/json" \ --data-binary @/tmp/u-fixed.json -w "%{http_code}\n" -o /dev/null # Expect: 204 ``` The convenience wrapper for all 5 users in one go is at `bin/force-english-all-users.sh` — read-modify-write loop, idempotent, prints each user's before/after state. --- ## Wrapper update for future users `bin/add-jellyfin-user.sh` step `[3/4]` currently sets `SubtitleMode`/`SubtitleLanguagePreference`/`AudioLanguagePreference`/ `PlayDefaultAudioTrack` on the new user's `Configuration`. Add `UICulture` to that same block: ```python c['SubtitleMode'] = 'Default' c['SubtitleLanguagePreference'] = 'eng' c['AudioLanguagePreference'] = 'eng' c['PlayDefaultAudioTrack'] = True c['UICulture'] = 'en-US' # NEW: pin UI to English regardless of browser Accept-Language ``` That is a one-line addition; the rest of the wrapper is untouched. --- ## What CANNOT be forced (limits) 1. **Pre-auth splash bundle strings.** Before the user logs in, the web SPA loads a translation file based on `navigator.language` / browser `Accept-Language`. The `