-
Notifications
You must be signed in to change notification settings - Fork 107
Add support for season and episode translations in TMDB and iTunes #1679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…ations Add extra information structs and JSONB columns so translation rows can be keyed by season/episode while keeping base translation uniqueness intact. This also registers a guarded migration that updates indexes for per-season/per-episode translations without breaking fresh installs.
…tions TMDB show translations now include season and episode names/overviews, while iTunes podcast translations surface episode titles and descriptions in the preferred language. The movie translation results are updated to populate the extended translation struct defaults.
…items Update the media translation service to write and read season/episode translation rows with extra metadata and reconstruct nested translation payloads. Translation-aware search and sorting now ignore episode-specific rows so list results remain stable for top-level titles.
Rename show/podcast translation indexes to stay under the identifier length limit and remove legacy or truncated variants during migration so fresh installs and upgrades do not collide.
Keep the migration focused on the current short index names so fresh and existing instances apply the same schema without extra compatibility logic in this development branch.
Extend the MediaTranslations query to include season and episode translation payloads so codegen captures the new schema fields. Regenerate the backend GraphQL artifacts to align frontend types with the updated query.
This reverts commit d2ee625.
This reverts commit 8ed9a61.
…uncation" This reverts commit f50a28a.
…r media items" This reverts commit bbfcba0.
… translations" This reverts commit 4c3c1bf.
…y translations" This reverts commit 30ee2b2.
…ties This commit removes the `has_translations_for_languages` column from metadata, metadata_group, and person tables as part of the transition to granular translation fetching. Changes: - Add migration m20260118_changes_for_issue_1672 to drop the columns - Update original table creation migrations to remove column definitions - Remove column from database models (metadata, metadata_group, person) - Clean up v10 migration to remove references to this column This change enables per-variant translation tracking instead of tracking all translations for an entity at once, allowing more efficient and granular translation fetching. Related to issue #1672. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Introduce a new cache mechanism to track ongoing translation fetch operations, preventing duplicate requests and enabling UI status updates. Changes: - Add MediaTranslationInProgressCacheInput struct with entity_id, entity_lot, variant, and language fields - Add ApplicationCacheKey::MediaTranslationInProgress variant - Add ApplicationCacheValue::MediaTranslationInProgress with EmptyCacheValue - Set TTL to 15 minutes for translation in-progress cache keys - Remove UserEntityTranslations cache key (replaced by per-query caching) This enables the frontend to differentiate between "not yet fetched" and "currently being fetched" states for individual translation variants, improving user experience during translation operations. Related to issue #1672. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Refactor MediaTranslationResult union from 3 variants to 2 by consolidating
InProgress and NotFetched into a single Pending variant with an enum status field.
Before:
- MediaTranslationValue { value }
- MediaTranslationInProgress { in_progress: bool }
- MediaTranslationNotFetched { not_fetched: bool }
After:
- MediaTranslationValue { value }
- MediaTranslationPending { status: MediaTranslationPendingStatus }
Where MediaTranslationPendingStatus is an enum with InProgress and NotFetched
variants.
This change eliminates redundant boolean fields and provides a cleaner,
more maintainable type structure. The enum-based approach is more idiomatic
and makes the pending states explicit without needing marker boolean fields.
Changes:
- Add MediaTranslationPendingStatus enum (InProgress, NotFetched)
- Add MediaTranslationPending struct with status field
- Update MediaTranslationResult union to use Pending variant
- Add MediaTranslationInput and MediaTranslationValue types for the new API
Related to issue #1672.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace bulk translation fetching with granular per-variant approach, allowing independent fetching of title, description, and image translations. Key changes: Service layer (media-translation): - Rename media_translations → media_translation (singular) - Remove cache_service wrapper; query directly returns MediaTranslationResult - Query entity_translation by (entity_id, entity_lot, language, variant) - Return MediaTranslationValue when row exists (value may be null) - Return MediaTranslationPending with InProgress status when cache key exists - Return MediaTranslationPending with NotFetched status otherwise - Add helper functions for entity source lookup and preferred language retrieval - Add translation_value_for_variant to extract specific variant from details Job handling (UpdateMediaTranslationJob): - Change payload from full entity to specific variant: user_id, entity_id, entity_lot, variant - Set in-progress cache key before enqueueing job - Skip enqueueing if cache key already exists (prevents duplicate fetches) - Call provider translation method for specific variant only - Insert/update single entity_translation row for the variant - Expire in-progress cache key after completion - Handle provider translation extraction per variant Resolver changes: - Update mutation input to accept MediaTranslationInput (entity + variant) - Remove CachedResponse wrapper from query return type - Compute preferred language from user preferences before job dispatch Background job model: - Simplify UpdateMediaTranslationJobInput to variant-specific fields - Remove EntityWithLot usage in favor of explicit entity_id/entity_lot/variant Dependencies: - Add chrono dependency to background-models for Utc timestamp - Update Cargo.lock with dependency changes This refactoring enables more efficient translation fetching by only requesting the specific translation variant needed (e.g., just the title), rather than fetching all variants at once. It also provides better UI feedback through the in-progress cache tracking. Related to issue #1672. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update GraphQL schema to support per-variant translation queries using the new MediaTranslationResult union with enum-based pending status. Query changes: - Rename MediaTranslations → MediaTranslation (singular) - Change input from EntityWithLotInput to MediaTranslationInput - Add variant field to input (EntityTranslationVariant) - Update response to use MediaTranslationResult union: * MediaTranslationValue with optional value field * MediaTranslationPending with status enum (InProgress | NotFetched) - Remove CachedResponse wrapper Mutation changes: - Update deployUpdateMediaTranslationsJob to accept MediaTranslationInput - Include variant field in mutation input Generated types: - Regenerate TypeScript types from updated schema - Update MediaTranslationQuery to handle new union structure - Add MediaTranslationPendingStatus enum type - Update fragment types for MediaTranslationValue and MediaTranslationPending This enables the frontend to query translations for specific variants (title, description, image) independently and receive clear status information about whether a translation is being fetched or not yet requested. Related to issue #1672. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ant hooks
Replace bulk translation fetching with granular per-variant approach in
the frontend, complementing the backend refactoring.
Hooks changes (apps/frontend/app/lib/shared/hooks.ts):
- Replace useTranslationMonitor with useTranslationValue hook
- useTranslationValue accepts variant parameter (Title, Description, Image)
- Supports MediaTranslationResult union with enum-based pending status
- Handles MediaTranslationPending with NotFetched and InProgress states
- Deploys translation job on NotFetched status
- Continues polling while InProgress
- Returns translation value when MediaTranslationValue received
- Update useMetadataDetails to fetch 3 separate translation variants
- Update usePersonDetails to fetch 3 separate translation variants
- Update useMetadataGroupDetails to fetch 3 separate translation variants
- Return translations as { title, description, image } object
Query factory changes (apps/frontend/app/lib/shared/react-query.ts):
- Rename entityTranslations → entityTranslation query key
- Add variant parameter to query key (EntityTranslationVariant)
- Update refreshEntityDetails to invalidate all translation variants
- Import EntityTranslationVariant type
Component compatibility:
- Existing components already use translations.title, translations.description,
translations.image syntax
- No component changes needed due to backwards-compatible return structure
- Components automatically benefit from granular fetching
This enables more efficient translation loading by fetching only the
specific variants needed (e.g., just title) rather than all at once,
with proper UI feedback through polling and status tracking.
Related to issue #1672.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Move `useTranslationValue` calls from detail hooks (useMetadataDetails, usePersonDetails, useMetadataGroupDetails) to the components where translations are actually needed. This change improves performance by only fetching translations when and where they are required, rather than pre-fetching all translation variants in the detail hooks. Changes: - Exported `useTranslationValue` hook from hooks.ts for use in components - Removed translation calls from useMetadataDetails, usePersonDetails, and useMetadataGroupDetails - these hooks now return only 2 values instead of 3 - Updated all consuming components to call useTranslationValue directly with the specific variant they need (Title, Description, or Image) Benefits: - Better performance: translations are only fetched when needed - More granular control: components can specify which translation variants they require - Clearer component dependencies: translation requirements are explicit at the point of use - Follows React best practices: hooks are called at the point where data is consumed Files updated: - lib/shared/hooks.ts: Exported useTranslationValue, removed translations from detail hooks - components/media/base-display.tsx: Added useTranslationValue calls - components/media/display-items.tsx: Added useTranslationValue calls for metadata, groups, and people - routes/_dashboard.media.item.$id._index.tsx: Added useTranslationValue calls for all three variants - routes/_dashboard.media.groups.item.$id._index.tsx: Added useTranslationValue calls for groups - routes/_dashboard.media.people.item.$id._index.tsx: Added useTranslationValue calls for people and nested metadata groups - routes/_dashboard.fitness.$entity.$id._index.tsx: Added useTranslationValue calls in ConsumedMetadataDisplay - components/routes/dashboard/forms/metadata-progress-update/progress-update.tsx: Added useTranslationValue for metadata title - components/routes/media-item/displays/metadata-creator.tsx: Added useTranslationValue calls for person translations Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…uple structs Move translation persistence into provider translate calls so the job handler only triggers translation and timestamps entities, while utilities handle database upserts centrally. Store SupportingService in base providers where possible to avoid duplicating fields, and keep wrapper services as tuple structs when they only carry a single base service for consistency and ergonomics.
…g redundant config parameter
…ers as tuple structs" This reverts commit adaf4c3.
This reverts commit 6ebab7a.
…ndex
The unique index on entity_translation table was only covering
(language, metadata_id, variant), which caused season/episode
translations to conflict with base translations since they share
the same metadata_id.
By adding show_extra_information and podcast_extra_information to the
unique index, translations for different seasons/episodes are now
properly distinguished:
- Base translation: (fr, id, Title, NULL, NULL)
- Season translation: (fr, id, Title, {season:1}, NULL)
- Episode translation: (fr, id, Title, {season:1,episode:1}, NULL)
PostgreSQL treats these as distinct index keys since JSONB values
are directly comparable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughRefactors media translation end-to-end: adds per-season/episode/podcast extra-info, replaces user-scoped cached translations with in-progress media translation caching, updates provider/trait signatures and DB/models, and adds frontend polling hooks with per-variant translation-value hooks. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as Frontend Component
participant Hook as useTranslationValue
participant Monitor as useEntityUpdateMonitor
participant API as GraphQL MediaTranslation
participant Service as Media Translation Service
participant Scheduler as Background Job Scheduler
participant Provider as Media Provider
participant DB as Database
User->>UI: opens page needing translation
UI->>Hook: request translation(entityId, variant, extraInfo)
Hook->>API: query mediaTranslation(input)
API-->>Hook: returns Value / Pending / NotFetched
alt Value
Hook-->>UI: return translation value
else Pending or NotFetched
Hook->>Monitor: start monitor
Monitor->>Service: deploy_update_media_translations_job(input)
Service->>Scheduler: schedule UpdateMediaTranslations job
Scheduler->>Provider: translate_metadata(identifier, lang, extraInfo)
Provider-->>Service: return translation details
Service->>DB: upsert entity_translation (with extra_info)
Monitor->>API: poll mediaTranslation periodically
API-->>Monitor: returns Value when ready
Monitor-->>Hook: notify updated value
Hook-->>UI: deliver translation value
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In
`@apps/frontend/app/components/routes/dashboard/forms/metadata-progress-update/media-types/podcast-form.tsx`:
- Around line 28-31: The Select's data prop can be undefined when
metadataDetails.podcastSpecifics is null; update the expression used for the
Select data (where you currently call
metadataDetails.podcastSpecifics?.episodes.map(...)) to provide a fallback array
(e.g., `|| []`) so the Select always receives an array — locate the Select in
podcast-form.tsx that builds its data from
metadataDetails.podcastSpecifics?.episodes and append the empty-array fallback
to the mapped expression.
In
`@apps/frontend/app/components/routes/dashboard/forms/metadata-progress-update/media-types/show-form.tsx`:
- Around line 27-30: The Season Select's data can be undefined when
metadataDetails.showSpecifics is null—add a defensive fallback so the data prop
always receives an array (mirror the Episode Select approach) by using
metadataDetails.showSpecifics and its seasons list when present, otherwise
supply an empty array; update the data expression for the Season Select (the
mapping over metadataDetails.showSpecifics?.seasons and the Season Select
component) to return [] if showSpecifics or seasons are missing.
In
`@apps/frontend/app/components/routes/dashboard/forms/metadata-progress-update/progress-update.tsx`:
- Line 39: Replace the logical OR fallback that uses || with nullish coalescing
?? so intentionally empty translations are preserved; specifically update the
JSX expression that currently reads metadataTitleTranslation ||
metadataDetails.title to use metadataTitleTranslation ?? metadataDetails.title
in the ProgressUpdate component (referencing metadataTitleTranslation and
metadataDetails.title) so only null/undefined trigger the fallback.
In `@apps/frontend/app/lib/shared/hooks.ts`:
- Around line 383-403: The inner function useMetadataTranslationValue defined
inside useMetadataDetails violates React Rules of Hooks because it calls
useTranslationValue from within another hook; extract it into a standalone
exported hook (useMetadataTranslationValue) that accepts metadataId, enabled,
variant, showExtraInformation, and podcastExtraInformation, call
useMetadataDetails(metadataId, enabled) inside the new hook to obtain
metadataDetailsQuery, then call useTranslationValue with entityId = metadataId,
entityLot = EntityLot.Metadata, mediaSource = metadataDetailsQuery.data?.source,
and the other props; remove the inner definition from useMetadataDetails and
update all callers to import and call the new top-level
useMetadataTranslationValue with the metadataId instead of using the returned
function.
In `@crates/migrations/sql/src/m20260118_changes_for_issue_1672.rs`:
- Around line 11-23: The unique index
"entity_translation__language_metadata_id_variant_idx" should use PostgreSQL
15's NULLS NOT DISTINCT so NULL values are treated as equal; update the CREATE
UNIQUE INDEX statement to list the indexed columns with NULLS NOT DISTINCT (e.g.
("language" NULLS NOT DISTINCT, "metadata_id" NULLS NOT DISTINCT, "variant"
NULLS NOT DISTINCT, "show_extra_information" NULLS NOT DISTINCT,
"podcast_extra_information" NULLS NOT DISTINCT)) and keep the existing WHERE
"metadata_id" IS NOT NULL clause so the index enforces the intended uniqueness
across NULLs.
In `@crates/services/miscellaneous/media-translation/src/lib.rs`:
- Around line 273-312: deploy_update_media_translations_job currently sets an
in-progress cache key via cache_service::set_key
(ApplicationCacheValue::MediaTranslationInProgress) but does not remove that
marker if ss.perform_application_job (MpApplicationJob::UpdateMediaTranslations)
fails; add cleanup so the cache_key is deleted on error to avoid a 15-minute
stale block: after calling ss.perform_application_job(...).await, catch any Err,
call cache_service::delete_key(ss, cache_key).await (or an equivalent removal
API) before propagating the error, ensuring the cache removal references the
same cache_key and value type used when setting the marker.
.../app/components/routes/dashboard/forms/metadata-progress-update/media-types/podcast-form.tsx
Show resolved
Hide resolved
...end/app/components/routes/dashboard/forms/metadata-progress-update/media-types/show-form.tsx
Show resolved
Hide resolved
.../frontend/app/components/routes/dashboard/forms/metadata-progress-update/progress-update.tsx
Outdated
Show resolved
Hide resolved
PostgreSQL treats NULLs as distinct by default in unique indexes, which would allow multiple base translations (where show_extra_information and podcast_extra_information are NULL) for the same (language, metadata_id, variant) combination. Adding NULLS NOT DISTINCT ensures the database enforces uniqueness even when the extra information columns are NULL, providing a safety net alongside the application's upsert logic. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ts and hooks" This reverts commit 51759b9.
This reverts commit 6dbbe4d.
…terval
The previous implementation of useEntityUpdateMonitor created independent
polling loops for each hook instance, leading to duplicate listeners when
multiple components used the same entity hooks (e.g., useMetadataDetails).
This caused multiple setTimeout chains, redundant job deployments, and
unnecessary refetch calls for the same entity.
Key issues addressed:
- Multiple components calling useMetadataDetails("abc") would create
separate polling loops, each deploying jobs and refetching independently
- useTranslationValue inside detail hooks created additional polling
instances for the same entity
- Manual setTimeout-based polling didn't leverage React Query's built-in
query observer coordination
Changes:
1. Replaced useEntityUpdateMonitor with useEntityDetailsPolling that returns
a refetchInterval function instead of managing polling directly
2. Moved polling logic to React Query's refetchInterval option, which
automatically deduplicates across multiple observers of the same query
3. Introduced useDeployJobOnce with a global Set tracker to ensure jobs
are deployed only once per entity, even across multiple hook instances
4. Updated useTranslationValue to use refetchInterval directly on its query
5. Simplified useMetadataDetails, usePersonDetails, and useMetadataGroupDetails
to use the new polling approach
Benefits:
- React Query naturally deduplicates refetch intervals across observers
- Single polling loop per query regardless of component count
- Global job tracker prevents duplicate deployments
- Cleaner code with less manual state management
- Eliminated setTimeout chains in favor of React Query's scheduler
Trade-offs:
- Global deployedJobs Set grows indefinitely (acceptable for now, can add
TTL if needed in long-running sessions)
- Job deployment uses raw clientGqlService.request instead of useMutation
(could be improved in future)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This reverts commit b64b239.
…efetchInterval" This reverts commit 0b69c1d.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/frontend/app/lib/shared/hooks/polling.ts`:
- Around line 20-25: The deploy helper createDeployMediaEntityJob currently
fires clientGqlService.request without returning the Promise, causing unhandled
rejections and leaving polling unaware of failures; update
createDeployMediaEntityJob to return the Promise from clientGqlService.request
(i.e., return clientGqlService.request(...)) so callers receive the Promise, and
then update the place where deployJob is invoked to await or .then/.catch the
returned Promise (or wrap in try/catch) to stop or signal the polling on
rejection. Apply the same change to the other similar helpers referenced around
lines 111-114 and 191-201 so all deploy-job creators return their requests and
callers handle errors.
♻️ Duplicate comments (1)
apps/frontend/app/lib/shared/hooks/index.ts (1)
195-215: Rules of Hooks violation: nested translation hooks.
Theseuse*TranslationValuefunctions call hooks but are defined inside another hook and returned. Consumers can’t guarantee consistent hook call order, so React may throw “Rendered more hooks than during the previous render.” Extract them as top-level hooks and update call sites.✅ Example extraction (repeat for person & metadata group)
export const useMetadataTranslationValue = (props: { metadataId?: string; enabled?: boolean; variant: EntityTranslationVariant; showExtraInformation?: ShowTranslationExtraInformationInput; podcastExtraInformation?: PodcastTranslationExtraInformationInput; }) => { const [metadataDetailsQuery] = useMetadataDetails( props.metadataId, props.enabled, ); return useTranslationValue({ entityId: props.metadataId, variant: props.variant, entityLot: EntityLot.Metadata, enabled: props.enabled, mediaSource: metadataDetailsQuery.data?.source, showExtraInformation: props.showExtraInformation, podcastExtraInformation: props.podcastExtraInformation, }); };Also applies to: 232-248, 274-295
…on value hooks The previous implementation violated React's Rules of Hooks by defining hooks inside other hooks (useMetadataDetails, usePersonDetails, useMetadataGroupDetails) and returning them to be called by consumers. This pattern breaks React's guarantee of consistent hook call order across renders, which can cause runtime errors like "Rendered more hooks than during the previous render". This commit refactors the code to follow React best practices by: 1. Extracting useMetadataTranslationValue, usePersonTranslationValue, and useMetadataGroupTranslationValue as standalone top-level hooks 2. Changing the return signatures of details hooks from 3-element tuples to 2-element tuples (removing the hook function) 3. Having the standalone translation hooks accept entity IDs as parameters and internally call the details hooks to get media source information This pattern ensures hooks are always called in a consistent order and at the top level of components, as required by React. The translation hooks now properly encapsulate the logic for fetching translation values while maintaining the same functionality. This addresses the critical issue raised in the code review comment at #1679 (comment)
…ne translation hooks Update all media display components to use the newly extracted standalone translation hooks instead of destructuring them from details hooks. Changes made to each component: - Import the standalone useMetadataTranslationValue, usePersonTranslationValue, or useMetadataGroupTranslationValue hooks - Remove the third element from details hook destructuring - Pass the appropriate entity ID (metadataId, personId, metadataGroupId) to translation value hooks Affected components: - MetadataDisplayItem: Now passes metadataId to translation hooks - MetadataGroupDisplayItem: Now passes metadataGroupId to translation hooks - PersonDisplayItem: Now passes personId to translation hooks - PartialMetadataDisplay: Updated for metadata translations - DisplayPodcastEpisode: Updated with metadataId parameter - DisplayShowEpisode: Updated with metadataId parameter - DisplayShowSeason: Updated with metadataId parameter This completes the migration to the Rules of Hooks compliant pattern, ensuring all translation hooks are called as proper top-level hooks with explicit entity IDs rather than being returned from other hooks.
…tion hooks Update the main media detail route pages to use the standalone translation hooks extracted in the previous commit. These pages are the primary consumers of translation functionality and needed significant updates. Media Item Details Page (_dashboard.media.item.$id._index.tsx): - Import useMetadataTranslationValue and useMetadataGroupTranslationValue - Update metadata details destructuring from 3 to 2 elements - Pass metadataId to all useMetadataTranslationValue calls for title, description, and image translations - Update metadata group translation call with metadataGroupId parameter Person Details Page (_dashboard.media.people.item.$id._index.tsx): - Import usePersonTranslationValue and useMetadataGroupTranslationValue - Update person details destructuring from 3 to 2 elements - Pass personId to all usePersonTranslationValue calls - Update MetadataGroupDisplay component to pass metadataGroupId to translation hooks Metadata Group Details Page (_dashboard.media.groups.item.$id._index.tsx): - Import useMetadataGroupTranslationValue - Update metadata group details destructuring from 3 to 2 elements - Pass metadataGroupId to all useMetadataGroupTranslationValue calls for title, description, and image translations These changes ensure that the main detail pages follow the Rules of Hooks by calling translation hooks as top-level functions with explicit entity IDs, maintaining proper React hook ordering and preventing runtime errors.
…ents Remove translation hook usage and imports from components that don't actually need translation functionality. These components were using the old pattern of destructuring translation hooks from details hooks but weren't calling the translation hooks, making the destructuring unnecessary. Components updated: - MetadataProgressUpdateForm: Removed unused useMetadataTranslationValue destructuring and EntityTranslationVariant import, now uses direct metadata title access - MetadataCreatorDisplay: Removed unused usePersonTranslationValue calls and EntityTranslationVariant import, now uses direct person details - DisplayShowSeasonEpisodesModal: Removed unused useMetadataTranslationValue destructuring, EntityTranslationVariant import, and translation logic - ConsumedMetadataDisplay (fitness page): Removed unused translation hooks and EntityTranslationVariant import, now uses direct metadata details These components either: 1. Don't display translated content at all 2. Display content in contexts where translations aren't needed (e.g., modal titles, internal forms) 3. Were carrying over the old destructuring pattern without actually using the translation functionality This cleanup reduces unnecessary hook calls and imports while maintaining the same functionality. The components now have cleaner code that more accurately reflects their actual dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx (1)
234-240: Inconsistent title usage in ReviewItemDisplay.The
titleprop usesmetadataGroupDetailsData.data.details.titledirectly, while other places in this file prefermetadataGroupTitleTranslationwith a fallback. Consider using the translated title for consistency.Suggested fix
<ReviewItemDisplay review={r} key={r.id} entityLot={EntityLot.MetadataGroup} entityId={loaderData.metadataGroupId} - title={metadataGroupDetailsData.data.details.title} + title={title} />apps/frontend/app/routes/_dashboard.media.people.item.$id._index.tsx (1)
365-373: Inconsistent title usage in reviews tab.
ReviewItemDisplayreceivestitle={personDetails.data.details.name}(untranslated) whilesetEntityToReviewat line 310 usespersonTitleTranslation || personDetails.data.details.name(translated). Consider using the translated title for consistency:♻️ Suggested fix
<ReviewItemDisplay review={r} key={r.id} entityLot={EntityLot.Person} entityId={loaderData.personId} - title={personDetails.data.details.name} + title={personTitleTranslation || personDetails.data.details.name} />apps/frontend/app/routes/_dashboard.media.item.$id._index.tsx (1)
1048-1056: Inconsistent title usage in reviews panel.
ReviewItemDisplayreceivestitle={metadataDetails.data.title}(untranslated) whilesetEntityToReviewat line 874 usesmetadataTitleTranslation || metadataDetails.data.title(translated). This creates inconsistency in how the title is displayed across the application.♻️ Suggested fix
<ReviewItemDisplay key={r.id} review={r} lot={metadataDetails.data.lot} entityLot={EntityLot.Metadata} entityId={loaderData.metadataId} - title={metadataDetails.data.title} + title={title} />This uses the already-computed
titlevariable from line 300 which includes the translation fallback.
🤖 Fix all issues with AI agents
In `@apps/frontend/app/components/media/base-display.tsx`:
- Around line 100-102: Replace boolean-OR fallbacks with nullish coalescing for
translation values: in base-display.tsx update the image expression to use
metadataImageTranslation ?? images.at(0) (instead of metadataImageTranslation ||
images.at(0)) and update the title expression to metadataTitleTranslation ??
metadataDetails?.title ?? undefined (instead of metadataTitleTranslation ||
metadataDetails?.title || undefined); keep link/props.metadataId unchanged. This
ensures empty strings from translations are preserved while still falling back
when the translation is null or undefined.
In `@apps/frontend/app/components/media/display-items.tsx`:
- Around line 118-119: Replace the boolean-OR fallbacks with nullish coalescing
so empty-string translations are preserved: in the JSX where image and title
props are set (the expressions using metadataImageTranslation || images.at(0)
and metadataTitleTranslation || metadataDetails?.title), change the operators to
?? so they only fall back when the translation is null or undefined (i.e., use
metadataImageTranslation ?? images.at(0) and metadataTitleTranslation ??
metadataDetails?.title).
- Around line 287-288: The two fallback expressions in display-items.tsx use ||
inconsistently; change the fallbacks for image and title to use nullish
coalescing (??) so personImageTranslation ?? images.at(0) and
personTitleTranslation ?? personDetails?.details.name are used (referencing the
personImageTranslation, images.at(0), personTitleTranslation, and
personDetails?.details.name symbols) to avoid incorrectly treating falsy but
valid values as null.
- Around line 209-212: Replace the loose-OR fallbacks with nullish coalescing to
avoid treating empty strings or falsey but valid values as missing: change the
expressions using || — specifically the image fallback
(metadataGroupImageTranslation || images.at(0)) and the title fallback
(metadataGroupTitleTranslation || metadataGroupDetails?.details.title) — to use
?? instead so they become metadataGroupImageTranslation ?? images.at(0) and
metadataGroupTitleTranslation ?? metadataGroupDetails?.details.title in the
display-items.tsx component.
In
`@apps/frontend/app/components/routes/media-item/displays/metadata-creator.tsx`:
- Line 26: The prop assignment uses a redundant fallback: replace the expression
"personDetails?.details.assets.remoteImages.at(0) || undefined" with just
"personDetails?.details.assets.remoteImages.at(0)" (in the metadata-creator
component where the image prop is set) to remove the unnecessary "|| undefined"
since Array.prototype.at(0) already yields undefined for empty arrays.
In `@apps/frontend/app/components/routes/media-item/displays/show-season.tsx`:
- Around line 13-14: The helper getShowSeasonDisplayName is duplicated; extract
it into a shared utility (e.g., create a utils.ts exporting
getShowSeasonDisplayName(season: Season, title: string) =>
`${season.seasonNumber}. ${title}`) and replace the local definitions in both
components (the one defining getShowSeasonDisplayName and the one in the
episodes modal) with imports from the new utility so both files reuse the single
implementation.
In
`@apps/frontend/app/components/routes/media-item/modals/show-season-episodes-modal.tsx`:
- Around line 9-10: The helper getShowSeasonDisplayName is duplicated; extract
it into a shared utility (e.g., a new function in a common utils file) and
replace both occurrences (this file's getShowSeasonDisplayName and the one in
show-season.tsx) with an import from that utility; update the usages in the
modal component and show-season.tsx to import the shared function (preserving
the signature getShowSeasonDisplayName(season: Season, title: string)) and
remove the duplicated definition from both files.
- Around line 24-26: The modal currently computes title using season.name
directly; replicate the translation logic from DisplayShowSeason by calling
useMetadataTranslationValue with metadataId (props.metadataId),
showExtraInformation memoized as ({ season: season?.seasonNumber }) and variant
EntityTranslationVariant.Title to produce seasonTitleTranslation, then pass
seasonTitleTranslation ?? season.name into getShowSeasonDisplayName inside the
existing useMemo (the const title useMemo) so the translated title is used when
available.
In `@apps/frontend/app/routes/_dashboard.media.groups.item`.$id._index.tsx:
- Around line 177-179: Replace the logical-or fallback for entityTitle with a
nullish-coalescing fallback to mirror the fixes for title and description: use
metadataGroupTitleTranslation ?? metadataGroupDetailsData.data.details.title
instead of metadataGroupTitleTranslation ||
metadataGroupDetailsData.data.details.title so empty strings don't trigger the
fallback; update the expression that sets entityTitle accordingly.
- Around line 96-102: The fallback logic for title and description uses logical
OR which treats empty strings as falsy; update the assignments for title and
description to use nullish coalescing (replace || with ??) so
metadataGroupTitleTranslation and metadataGroupDescriptionTranslation only fall
back to metadataGroupDetailsData.data?.details.title/description when the
translations are null or undefined; adjust the expressions involving
metadataGroupTitleTranslation, metadataGroupDetailsData.data?.details.title,
metadataGroupDescriptionTranslation, and
metadataGroupDetailsData.data?.details.description accordingly.
apps/frontend/app/components/routes/media-item/displays/metadata-creator.tsx
Outdated
Show resolved
Hide resolved
apps/frontend/app/components/routes/media-item/displays/show-season.tsx
Outdated
Show resolved
Hide resolved
apps/frontend/app/components/routes/media-item/modals/show-season-episodes-modal.tsx
Outdated
Show resolved
Hide resolved
apps/frontend/app/components/routes/media-item/modals/show-season-episodes-modal.tsx
Show resolved
Hide resolved
apps/frontend/app/routes/_dashboard.media.groups.item.$id._index.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/frontend/app/components/routes/media-item/displays/show-season.tsx`:
- Around line 52-59: Replace nullish coalescing with logical OR for translation
fallbacks: change the two occurrences where seasonDescriptionTranslation ??
props.season.overview and seasonTitleTranslation ?? props.season.name are used
so they use || instead (i.e., seasonDescriptionTranslation ||
props.season.overview and seasonTitleTranslation || props.season.name) to ensure
empty-string translations fall back to the original values passed into overview
and getShowSeasonDisplayName.
| overview={seasonDescriptionTranslation ?? props.season.overview} | ||
| endDate={props.season.episodes.at(-1)?.publishDate} | ||
| startDate={props.season.episodes.at(0)?.publishDate} | ||
| runtime={sum(props.season.episodes.map((e) => e.runtime || 0))} | ||
| name={getShowSeasonDisplayName( | ||
| props.season, | ||
| seasonTitleTranslation ?? props.season.name, | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use || instead of ?? for translation fallbacks.
Based on learnings from this codebase, translation fallbacks should use logical OR (||) to ensure empty string translations fall back to the original value. Nullish coalescing (??) would preserve empty strings, potentially displaying blank content.
🔧 Suggested fix
- overview={seasonDescriptionTranslation ?? props.season.overview}
+ overview={seasonDescriptionTranslation || props.season.overview}
endDate={props.season.episodes.at(-1)?.publishDate}
startDate={props.season.episodes.at(0)?.publishDate}
runtime={sum(props.season.episodes.map((e) => e.runtime || 0))}
name={getShowSeasonDisplayName(
props.season,
- seasonTitleTranslation ?? props.season.name,
+ seasonTitleTranslation || props.season.name,
)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| overview={seasonDescriptionTranslation ?? props.season.overview} | |
| endDate={props.season.episodes.at(-1)?.publishDate} | |
| startDate={props.season.episodes.at(0)?.publishDate} | |
| runtime={sum(props.season.episodes.map((e) => e.runtime || 0))} | |
| name={getShowSeasonDisplayName( | |
| props.season, | |
| seasonTitleTranslation ?? props.season.name, | |
| )} | |
| overview={seasonDescriptionTranslation || props.season.overview} | |
| endDate={props.season.episodes.at(-1)?.publishDate} | |
| startDate={props.season.episodes.at(0)?.publishDate} | |
| runtime={sum(props.season.episodes.map((e) => e.runtime || 0))} | |
| name={getShowSeasonDisplayName( | |
| props.season, | |
| seasonTitleTranslation || props.season.name, | |
| )} |
🤖 Prompt for AI Agents
In `@apps/frontend/app/components/routes/media-item/displays/show-season.tsx`
around lines 52 - 59, Replace nullish coalescing with logical OR for translation
fallbacks: change the two occurrences where seasonDescriptionTranslation ??
props.season.overview and seasonTitleTranslation ?? props.season.name are used
so they use || instead (i.e., seasonDescriptionTranslation ||
props.season.overview and seasonTitleTranslation || props.season.name) to ensure
empty-string translations fall back to the original values passed into overview
and getShowSeasonDisplayName.
Implement per-season and per-episode translation metadata storage and retrieval for shows and podcasts. Update TMDB and iTunes providers to return translations for season and episode titles and summaries. Enhance the media translation service to handle granular fetching of translations, improving user experience during translation operations. Adjust GraphQL schema and frontend hooks to support the new translation structure. This addresses issues with missing translations and improves synchronization for TV show episodes.
Fixes #1672
Summary by CodeRabbit
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.