From d2120c636fd29511ca57f07fe57148a6e72fc195 Mon Sep 17 00:00:00 2001 From: s8n Date: Fri, 8 May 2026 17:04:03 +0100 Subject: [PATCH] =?UTF-8?q?web:=20english-lockdown=20shim=20=E2=80=94=20pi?= =?UTF-8?q?n=20locale=20+=20hide=20switchers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/inject-shim.py | 101 +++++++++++++++++++- web-overrides/ENGLISH-LOCKDOWN.md | 137 ++++++++++++++++++++++++++ web-overrides/index.html | 154 +++++++++++++++++++++++++++--- 3 files changed, 378 insertions(+), 14 deletions(-) create mode 100644 web-overrides/ENGLISH-LOCKDOWN.md diff --git a/bin/inject-shim.py b/bin/inject-shim.py index ab128da..950869a 100644 --- a/bin/inject-shim.py +++ b/bin/inject-shim.py @@ -14,6 +14,104 @@ SHIM = MARKER_BEGIN + r""" (function(){ var TITLE = 'ARRFLIX'; var BARE_RE = /^Jellyfin$/i; + /* === English-lockdown (synchronous, runs before Jellyfin bundle) === + Pins UI locale to en-US so the SPA never reads navigator.language + or the user's stored preference. Belt-and-braces against: + - localStorage keys the SPA reads on boot + - navigator.language / navigator.languages getters + - fetch / XHR Accept-Language header (best-effort; most browsers + block JS from setting it, but Jellyfin sometimes does) + - user-config save round-trip (rewrite UICulture → en-US before send) */ + try { + var LS_KEYS = ['appLanguage','selectedlanguage','selectedlocale','language','locale','culture']; + for (var i=0;i Browser-side belt for the per-user `UICulture` pin documented in +> `docs/15-force-english.md`. The server-side POST sets the authoritative +> value; this shim removes every escape hatch the SPA exposes to the user +> so they can't unpin it from the browser. + +Last verified: 2026-05-08 against Jellyfin 10.10.3 web bundle, arrflix.s8n.ru. + +--- + +## What this does + +The English-lockdown logic lives **inside the existing ARRFLIX runtime +shim** (one self-contained IIFE per `docs/10-spa-runtime-shim.md` — +"the shim must remain self-contained"). Source of truth: +`bin/inject-shim.py`. Compiled output lands in `web-overrides/index.html` +between the `ARRFLIX-SHIM-BEGIN` / `-END` markers. + +It runs **synchronously, before the Jellyfin bundle parses**, and pins +the UI to English by: + +| # | Mechanism | Why | +|---|-----------|-----| +| 1 | `localStorage.setItem` for `appLanguage`, `selectedlanguage`, `selectedlocale`, `language`, `locale`, `culture` → all set to `en-US` | Belt-and-braces; covers every key Jellyfin web has shipped under across versions. | +| 2 | `Object.defineProperty(Navigator.prototype, 'language' / 'languages')` returning `'en-US'` / `['en-US','en']` | Jellyfin's pre-auth bundle reads `navigator.language` to pick the splash translation file. Overriding the prototype getter beats the bundle to it. | +| 3 | `fetch` wrapper — strips `Accept-Language` header on outbound requests; rewrites `POST /Users/{id}/Configuration` body to force `UICulture: 'en-US'` before send | Defensive: even if a future Jellyfin build offers a "save language" UI we don't know about, the POST gets rewritten in-flight. The user can't opt out. | +| 4 | `XMLHttpRequest` wrapper — same Accept-Language strip + same Configuration POST rewrite | Older Jellyfin bundle code paths use XHR rather than fetch. Belt for the fetch suspenders. | +| 5 | `pinLocale()` re-runs on every `start()` call AND on the existing 1s setInterval safety net | Re-pin storage keys if the SPA tries to clear/rewrite them. | + +A companion CSS block in the existing critical-path `