feat(tmdb): series production-status chip and person death dates (#1240)

Two fields TMDB already sends us and the merge threw away — no new API
calls, no cache-key bump, they light up on existing cached payloads.

Series detail views (Xtream and Stalker) gain a production-status chip:
"Ended" tells you a show is finished before you commit to it, "Returning"
that it is not. TMDB returns `status` as an ENGLISH string even under
language=ru-RU, so it is normalized to a stable token
(normalizeSeriesStatus) and rendered through translated labels
(seriesStatusLabelKey). Unknown values are dropped rather than shown, so
a status TMDB adds later can never leak raw English into 19 locales.

Person pages render `deathday`, which mapPersonProfile has always parsed
into ActorProfile and no template ever read.

i18n: 7 keys across all 19 locales via the tools/i18n workflow.
Tests: status normalization (token mapping, case-insensitivity, the
British "cancelled" spelling, unknown/missing dropped).
Docs: tmdb-metadata-enrichment.md, CLAUDE.md.

Refs docs/architecture/tmdb-roadmap.md C1 and the zero-extra-call tier.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 4.8 authored and GitHub committed 2026-07-25 15:50:56 +02:00
1 parent cfa602d5b1
commit 8e1320cb34
34 files changed
+294 -9

No files matched your search

@@ -1,6 +1,7 @@
import {
TmdbEnrichedCastMember,
TmdbRecommendation,
TmdbSeriesStatus,
} from './tmdb.interface';
/**
@@ -41,6 +42,8 @@ export interface StalkerVodInfo {
tmdb_cast?: TmdbEnrichedCastMember[];
/** Directors (movies) / creators (series) as clickable person chips */
tmdb_directors?: TmdbEnrichedCastMember[];
/** Series production status (token, never a raw TMDB string) */
tmdb_status?: TmdbSeriesStatus;
/** TMDB backdrop URL — Stalker portals never provide one themselves */
tmdb_backdrop?: string;
/** YouTube trailer key from TMDB — Stalker portals provide no trailers */
@@ -33,6 +33,51 @@ export const DEFAULT_TMDB_SETTINGS: TmdbSettings = {
* Cast member attached to provider detail objects after TMDB enrichment.
* Rendered as avatar chips in the VOD/series detail views.
*/
/**
* Production status of a series, as a stable token. TMDB returns this
* field as an ENGLISH string regardless of the request language, so the
* raw value must never reach the UI — it is normalized here and rendered
* through translated labels.
*/
export type TmdbSeriesStatus =
| 'returning'
| 'planned'
| 'in-production'
| 'ended'
| 'canceled'
| 'pilot';
const SERIES_STATUS_TOKENS: Record<string, TmdbSeriesStatus> = {
'returning series': 'returning',
planned: 'planned',
'in production': 'in-production',
ended: 'ended',
canceled: 'canceled',
cancelled: 'canceled',
pilot: 'pilot',
};
/** `null` for unknown values — an unmapped status is simply not shown */
export function normalizeSeriesStatus(
status: string | null | undefined
): TmdbSeriesStatus | null {
const key = status?.trim().toLowerCase();
return (key && SERIES_STATUS_TOKENS[key]) || null;
}
const SERIES_STATUS_LABEL_KEYS: Record<TmdbSeriesStatus, string> = {
returning: 'XTREAM.SERIES_STATUS_RETURNING',
planned: 'XTREAM.SERIES_STATUS_PLANNED',
'in-production': 'XTREAM.SERIES_STATUS_IN_PRODUCTION',
ended: 'XTREAM.SERIES_STATUS_ENDED',
canceled: 'XTREAM.SERIES_STATUS_CANCELED',
pilot: 'XTREAM.SERIES_STATUS_PILOT',
};
export function seriesStatusLabelKey(status: TmdbSeriesStatus): string {
return SERIES_STATUS_LABEL_KEYS[status];
}
export interface TmdbEnrichedCastMember {
name: string;
character?: string;
@@ -1,6 +1,7 @@
import {
TmdbEnrichedCastMember,
TmdbRecommendation,
TmdbSeriesStatus,
} from './tmdb.interface';
export interface XtreamSerieDetails {
@@ -28,6 +29,8 @@ export interface XtreamSerieInfo {
tmdb_cast?: TmdbEnrichedCastMember[];
/** Directors (movies) / creators (series) as clickable person chips */
tmdb_directors?: TmdbEnrichedCastMember[];
/** Series production status (token, never a raw TMDB string) */
tmdb_status?: TmdbSeriesStatus;
/** Populated by TMDB enrichment; matched against the catalog in views */
tmdb_recommendations?: TmdbRecommendation[];
/** Matched TMDB show id — enables lazy season/episode enrichment */