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 `