feat(plex): every card rail in the reader's language; an episode in a poster rail stands as its show (E30 S1b)

The grid served Hungarian titles, but every other card surface (Discover rails, the
show page's episodes, search, collections, the person page, playlists, Home's resume
rail, hero and comfort shelf) served English. The card serializers now take a
required keyword-only lang (a forgotten call site fails instead of going English), and
each of those reads takes ?lang=hu, with the language in its query key. A Hungarian
search finds an episode by its Hungarian title too.

An episode on a poster rail showed its 16:9 still cropped into a poster, titled only
with the episode name; it now shows the show's poster and name, with S·E · title under
it (option A). Plex's generic season names (Season 1, Specials) are rebuilt from the
number in the reader's language; a season with a name of its own keeps it.

Home's comfort-shelf move wrote its optimistic order to the key without the language,
which nothing read any more; it writes the query's own key.
This commit is contained in:
2026-09-29 02:12:32 +02:00
parent 13d95ef70e
commit 632e39d458
37 changed files with 673 additions and 203 deletions
+108 -1
View File
@@ -12,7 +12,18 @@ import app.state as state
from app.auth import current_user
from app.db import get_db
from app.main import app
from app.models import PlexItem, PlexLibrary, User
from app import sysconfig
from app.models import (
PlexCollection,
PlexItem,
PlexLibrary,
PlexPlaylist,
PlexPlaylistItem,
PlexSeason,
PlexShow,
PlexState,
User,
)
def _seed(db):
@@ -162,3 +173,99 @@ def test_rating_filter_prefers_imdb_over_audience(db, monkeypatch, override_dep)
assert "High IMDb" in _titles(hit)
miss = _get(db, user, monkeypatch, override_dep, "/api/plex/library?scope=movie&rating_min=9")
assert "High IMDb" not in _titles(miss)
def _seed_rails(db):
"""`_seed` plus what every card rail reads: a HU-overlaid show with a HU-overlaid episode in
progress, a cast name, a collection, and a playlist holding the movie and the episode."""
user = _seed(db)
m1 = db.query(PlexItem).filter_by(rating_key="m1").one()
m1.cast_names = ["Anna"]
m1.collection_keys = ["c1"]
db.add(PlexCollection(rating_key="c1", library_id=m1.library_id, title="Coll"))
tv = PlexLibrary(plex_key="2", title="TV Shows", kind="show", enabled=True)
db.add(tv)
db.flush()
show = PlexShow(
rating_key="s1",
library_id=tv.id,
title="English Show",
title_hu="Magyar Sorozat",
added_at=datetime(2024, 1, 3, tzinfo=timezone.utc),
)
db.add(show)
db.flush()
season = PlexSeason(rating_key="s1s1", show_id=show.id, title="Season 1", season_number=1)
db.add(season)
db.flush()
ep = PlexItem(
rating_key="e1",
library_id=tv.id,
kind="episode",
show_id=show.id,
season_id=season.id,
title="Pilot",
title_hu="Kezdet",
season_number=1,
episode_number=1,
duration_s=3000,
)
db.add(ep)
db.flush()
db.add(
PlexState(
user_id=user.id,
item_id=ep.id,
status="in_progress",
position_seconds=600,
progress_updated_at=datetime(2024, 2, 1, tzinfo=timezone.utc),
)
)
pl = PlexPlaylist(user_id=user.id, title="Mix")
db.add(pl)
db.flush()
db.add_all(
[
PlexPlaylistItem(playlist_id=pl.id, item_id=m1.id, position=0),
PlexPlaylistItem(playlist_id=pl.id, item_id=ep.id, position=1),
]
)
sysconfig.set_value(db, "plex_enabled", True)
db.commit()
return user, pl.id
def test_every_card_rail_serves_the_readers_language(db, monkeypatch, override_dep):
"""E30: the grid spoke Hungarian, but every other card surface (Discover rails, show page,
search, collection, person page, playlist, Home) served the English title to a HU reader."""
user, pid = _seed_rails(db)
def get(path):
return _get(db, user, monkeypatch, override_dep, path).json()
def titles(cards):
return {c["title"] for c in cards}
rails = get("/api/plex/discover?scope=both&lang=hu")
assert {"Magyar Cím", "Magyar Sorozat"} <= titles(rails["recently_added"])
assert "Magyar Sorozat" in titles(rails["continue_watching"])
assert titles(get("/api/plex/show/s1?lang=hu")["seasons"][0]["episodes"]) == {"Kezdet"}
found = get("/api/plex/library?scope=both&lang=hu&q=Kezdet")["episodes"]
assert [(e["title"], e["show_title"]) for e in found] == [("Kezdet", "Magyar Sorozat")]
assert titles(get("/api/plex/collections/c1?lang=hu")["items"]) == {"Magyar Cím"}
assert titles(get("/api/plex/person/Anna?lang=hu")["movies"]) == {"Magyar Cím"}
items = get(f"/api/plex/playlists/{pid}?lang=hu")["items"]
assert [(c["title"], c.get("show_title")) for c in items] == [
("Magyar Cím", None),
("Kezdet", "Magyar Sorozat"),
]
home = get("/api/discover?lang=hu")["continue_watching"]
plex = [c for c in home if c["source"] == "plex"]
assert [c["title"] for c in plex] == ["Magyar Sorozat"]
assert plex[0]["next"]["title"] == "Kezdet"
# EN (the default) still serves English on the same rails.
items = get(f"/api/plex/playlists/{pid}")["items"]
assert [(c["title"], c.get("show_title")) for c in items] == [
("English Title", None),
("Pilot", "English Show"),
]