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:
@@ -50,3 +50,16 @@ describe("discoverApi query strings", () => {
|
||||
expect(lastUrl()).not.toContain("exclude=");
|
||||
});
|
||||
});
|
||||
|
||||
describe("the reader's language (E30)", () => {
|
||||
it("asks for Hungarian Plex cards on every Home read, and leaves English implied", async () => {
|
||||
await discoverApi.discover("any", 0, "hu");
|
||||
expect(lastUrl()).toContain("lang=hu");
|
||||
await discoverApi.discoverPick("any", 1, [], 0, "hu");
|
||||
expect(lastUrl()).toContain("lang=hu");
|
||||
await discoverApi.comfortPins("hu");
|
||||
expect(lastUrl()).toContain("lang=hu");
|
||||
await discoverApi.discover("any", 0, "en");
|
||||
expect(lastUrl()).not.toContain("lang=");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,8 +71,9 @@ export interface DiscoverPayload {
|
||||
}
|
||||
|
||||
export const discoverApi = {
|
||||
discover: (budget: DiscoverBudget, seed: number): Promise<DiscoverPayload> =>
|
||||
req(`/api/discover?budget=${budget}&seed=${seed}`),
|
||||
// `lang` "hu" serves a Plex card's Hungarian title (E30); en is the server default.
|
||||
discover: (budget: DiscoverBudget, seed: number, lang?: string): Promise<DiscoverPayload> =>
|
||||
req(`/api/discover?budget=${budget}&seed=${seed}${lang === "hu" ? "&lang=hu" : ""}`),
|
||||
// The "Mást kérek" reroll: a new hero alone, so pressing it does not re-fetch the rails under the
|
||||
// user's cursor.
|
||||
// `exclude` is the rolling list of `<source>:<id>` keys already shown. Without it a reroll only
|
||||
@@ -84,12 +85,15 @@ export const discoverApi = {
|
||||
seed: number,
|
||||
exclude: string[] = [],
|
||||
press = 0,
|
||||
lang?: string,
|
||||
): Promise<{ budget: DiscoverBudget; hero: DiscoverHero | null }> =>
|
||||
req(
|
||||
`/api/discover/pick?budget=${budget}&seed=${seed}&start=${press}` +
|
||||
(exclude.length ? `&exclude=${encodeURIComponent(exclude.join(","))}` : ""),
|
||||
(exclude.length ? `&exclude=${encodeURIComponent(exclude.join(","))}` : "") +
|
||||
(lang === "hu" ? "&lang=hu" : ""),
|
||||
),
|
||||
comfortPins: (): Promise<{ items: DiscoverCard[] }> => req("/api/discover/pins"),
|
||||
comfortPins: (lang?: string): Promise<{ items: DiscoverCard[] }> =>
|
||||
req(`/api/discover/pins${lang === "hu" ? "?lang=hu" : ""}`),
|
||||
addComfortPin: (
|
||||
kind: PinKind,
|
||||
ref: string,
|
||||
|
||||
@@ -495,12 +495,13 @@ export const plexApi = {
|
||||
},
|
||||
// One collection's hero + its members in the requested order (the Collection info page). The sort
|
||||
// vocabulary is the browse grid's, so the same Sort control means the same thing on both.
|
||||
plexCollection: (rk: string, sort?: SortState): Promise<PlexCollectionDetail> => {
|
||||
plexCollection: (rk: string, sort?: SortState, lang?: string): Promise<PlexCollectionDetail> => {
|
||||
const u = new URLSearchParams();
|
||||
if (sort) {
|
||||
u.set("sort", sort.key);
|
||||
u.set("sort_dir", sort.dir);
|
||||
}
|
||||
if (lang === "hu") u.set("lang", "hu");
|
||||
const qs = u.toString();
|
||||
return req(`/api/plex/collections/${encodeURIComponent(rk)}${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
@@ -552,7 +553,8 @@ export const plexApi = {
|
||||
const qs = p.toString();
|
||||
return req(`/api/plex/playlists${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
plexPlaylist: (id: number): Promise<PlexPlaylistDetail> => req(`/api/plex/playlists/${id}`),
|
||||
plexPlaylist: (id: number, lang?: string): Promise<PlexPlaylistDetail> =>
|
||||
req(`/api/plex/playlists/${id}${lang === "hu" ? "?lang=hu" : ""}`),
|
||||
plexCreatePlaylist: (title: string, item_rating_key?: string): Promise<PlexPlaylist> =>
|
||||
req(`/api/plex/playlists`, {
|
||||
method: "POST",
|
||||
@@ -636,8 +638,8 @@ export const plexApi = {
|
||||
`/api/plex/people/cards?scope=${encodeURIComponent(scope)}&names=${encodeURIComponent(names.join(","))}`,
|
||||
),
|
||||
// One cast/crew member's filmography: cached headshot + movies and shows (cast OR directors).
|
||||
plexPerson: (name: string): Promise<PlexPersonDetail> =>
|
||||
req(`/api/plex/person/${encodeURIComponent(name)}`),
|
||||
plexPerson: (name: string, lang?: string): Promise<PlexPersonDetail> =>
|
||||
req(`/api/plex/person/${encodeURIComponent(name)}${lang === "hu" ? "?lang=hu" : ""}`),
|
||||
plexSession: (
|
||||
id: string,
|
||||
start = 0,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// E30: every Plex read that returns cards carries the reader's language, so the server can serve the
|
||||
// Hungarian titles. The grid and the detail pages always did; these reads did not.
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { plexApi } from "./plex";
|
||||
|
||||
const fetchMock = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
json: async () => ({}),
|
||||
text: async () => "{}",
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
});
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
const lastUrl = (): string => {
|
||||
const calls = fetchMock.mock.calls;
|
||||
return String(calls[calls.length - 1]?.[0]);
|
||||
};
|
||||
|
||||
describe("Plex card reads in the reader's language", () => {
|
||||
it.each([
|
||||
["a playlist", () => plexApi.plexPlaylist(5, "hu"), () => plexApi.plexPlaylist(5, "en")],
|
||||
["a person", () => plexApi.plexPerson("Anna", "hu"), () => plexApi.plexPerson("Anna", "en")],
|
||||
[
|
||||
"a collection",
|
||||
() => plexApi.plexCollection("c1", { key: "title", dir: "asc" }, "hu"),
|
||||
() => plexApi.plexCollection("c1", { key: "title", dir: "asc" }, "en"),
|
||||
],
|
||||
])("%s asks for Hungarian, and leaves English implied", async (_what, hu, en) => {
|
||||
await hu();
|
||||
expect(lastUrl()).toContain("lang=hu");
|
||||
await en();
|
||||
expect(lastUrl()).not.toContain("lang=");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user