diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx
index 73b586c..c53d020 100644
--- a/src/components/metadata/HeroSection.tsx
+++ b/src/components/metadata/HeroSection.tsx
@@ -51,6 +51,7 @@ import { useToast } from '../../contexts/ToastContext';
import { useTraktContext } from '../../contexts/TraktContext';
import { useSettings } from '../../hooks/useSettings';
import { useTrailer } from '../../contexts/TrailerContext';
+import { useTranslation } from 'react-i18next';
import { logger } from '../../utils/logger';
import { TMDBService } from '../../services/tmdbService';
import TrailerService from '../../services/trailerService';
@@ -149,6 +150,7 @@ const ActionButtons = memo(({
onToggleCollection?: () => void;
}) => {
const { currentTheme } = useTheme();
+ const { t } = useTranslation();
const { showSaved, showTraktSaved, showRemoved, showTraktRemoved, showSuccess, showInfo } = useToast();
// Performance optimization: Cache theme colors
@@ -235,9 +237,9 @@ const ActionButtons = memo(({
// Show appropriate toast
if (wasInCollection) {
- showInfo('Removed from Collection', 'Removed from your Trakt collection');
+ showInfo(t('metadata.removed_from_collection_hero'), t('metadata.removed_from_collection_desc_hero'));
} else {
- showSuccess('Added to Collection', 'Added to your Trakt collection');
+ showSuccess(t('metadata.added_to_collection_hero'), t('metadata.added_to_collection_desc_hero'));
}
}, [onToggleCollection, isInCollection, showSuccess, showInfo]);
@@ -263,7 +265,7 @@ const ActionButtons = memo(({
const finalPlayButtonText = useMemo(() => {
// For movies, handle watched state
if (type === 'movie') {
- return isWatched ? 'Watch Again' : playButtonText;
+ return isWatched ? t('metadata.watch_again') : playButtonText;
}
// For series, validate next episode existence for both watched and resume cases
@@ -306,7 +308,7 @@ const ActionButtons = memo(({
return `Play S${seasonStr}E${episodeStr}`;
} else {
// If next episode doesn't exist, show generic text
- return 'Completed';
+ return t('metadata.completed');
}
} else {
// For non-watched episodes, check if current episode exists
@@ -320,17 +322,17 @@ const ActionButtons = memo(({
return playButtonText;
} else {
// Current episode doesn't exist, fallback to generic play
- return 'Play';
+ return t('metadata.play');
}
}
}
// Fallback label if parsing fails
- return isWatched ? 'Play Next Episode' : playButtonText;
+ return isWatched ? t('metadata.play_next_episode') : playButtonText;
}
// Default fallback for non-series or missing data
- return isWatched ? 'Play' : playButtonText;
+ return isWatched ? t('metadata.play') : playButtonText;
}, [isWatched, playButtonText, type, watchProgress, groupedEpisodes]);
// Count additional buttons (excluding Play and Save) - AI Chat no longer counted
@@ -394,7 +396,7 @@ const ActionButtons = memo(({
color={inLibrary ? (isAuthenticated && isInWatchlist ? "#E74C3C" : currentTheme.colors.white) : currentTheme.colors.white}
/>
- {inLibrary ? 'Saved' : 'Save'}
+ {inLibrary ? t('metadata.saved') : t('metadata.save')}
@@ -484,6 +486,7 @@ const WatchProgressDisplay = memo(({
trailerReady: boolean;
}) => {
const { currentTheme } = useTheme();
+ const { t } = useTranslation();
const { isAuthenticated: isTraktAuthenticated, forceSyncTraktProgress } = useTraktContext();
// State to trigger refresh after manual sync
@@ -567,7 +570,7 @@ const WatchProgressDisplay = memo(({
progressPercent: 100,
formattedTime: watchedDate,
episodeInfo,
- displayText: watchedViaTrakt ? 'Watched on Trakt' : 'Watched',
+ displayText: watchedViaTrakt ? t('metadata.watched_on_trakt') : t('metadata.watched'),
syncStatus: isTraktAuthenticated && watchProgress?.traktSynced ? '' : '', // Clean look for watched
isTraktSynced: watchProgress?.traktSynced && isTraktAuthenticated,
isWatched: true
@@ -597,22 +600,22 @@ const WatchProgressDisplay = memo(({
}
// Enhanced display text with Trakt integration
- let displayText = progressPercent >= 85 ? 'Watched' : `${Math.round(progressPercent)}% watched`;
+ let displayText = progressPercent >= 85 ? t('metadata.watched') : t('metadata.percent_watched', { percent: Math.round(progressPercent) });
let syncStatus = '';
// Show Trakt sync status if user is authenticated
if (isTraktAuthenticated) {
if (isUsingTraktProgress) {
- syncStatus = ' • Using Trakt progress';
+ syncStatus = ' • ' + t('metadata.using_trakt_progress');
if (watchProgress.traktSynced) {
- syncStatus = ' • Synced with Trakt';
+ syncStatus = ' • ' + t('metadata.synced_with_trakt_progress');
}
} else if (watchProgress.traktSynced) {
- syncStatus = ' • Synced with Trakt';
+ syncStatus = ' • ' + t('metadata.synced_with_trakt_progress');
// If we have specific Trakt progress that differs from local, mention it
if (watchProgress.traktProgress !== undefined &&
Math.abs(progressPercent - watchProgress.traktProgress) > 5) {
- displayText = `${Math.round(progressPercent)}% watched (${Math.round(watchProgress.traktProgress)}% on Trakt)`;
+ displayText = t('metadata.percent_watched_trakt', { percent: Math.round(progressPercent), traktPercent: Math.round(watchProgress.traktProgress) });
}
} else {
// Do not show "Sync pending" label anymore; leave status empty.
diff --git a/src/components/metadata/SeriesContent.tsx b/src/components/metadata/SeriesContent.tsx
index caa8d2e..328db39 100644
--- a/src/components/metadata/SeriesContent.tsx
+++ b/src/components/metadata/SeriesContent.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useState, useRef, useCallback, useMemo, memo } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator, Dimensions, useWindowDimensions, useColorScheme, FlatList, Modal, Pressable } from 'react-native';
+import { useTranslation } from 'react-i18next';
import * as Haptics from 'expo-haptics';
import FastImage from '@d11/react-native-fast-image';
import { MaterialIcons } from '@expo/vector-icons';
@@ -54,6 +55,7 @@ const SeriesContentComponent: React.FC = ({
}) => {
const { currentTheme } = useTheme();
const { settings } = useSettings();
+ const { t } = useTranslation();
const { width } = useWindowDimensions();
const isDarkMode = useColorScheme() === 'dark';
@@ -740,7 +742,7 @@ const SeriesContentComponent: React.FC = ({
return (
- Loading episodes...
+ {t('metadata.loading_episodes')}
);
}
@@ -749,7 +751,7 @@ const SeriesContentComponent: React.FC = ({
return (
- No episodes available
+ {t('metadata.no_episodes_available')}
);
}
@@ -785,7 +787,7 @@ const SeriesContentComponent: React.FC = ({
color: currentTheme.colors.highEmphasis,
fontSize: isTV ? 28 : isLargeTablet ? 26 : isTablet ? 24 : 18
}
- ]}>Seasons
+ ]}>{t('metadata.seasons')}
{/* Dropdown Toggle Button */}
{
const handleSpoilerPress = useCallback((comment: any) => {
Alert.alert(
- 'Spoiler Warning',
- 'This comment contains spoilers. Are you sure you want to reveal it?',
+ t('metadata.spoiler_warning'),
+ t('metadata.spoiler_warning_desc'),
[
{
- text: 'Cancel',
+ text: t('metadata.cancel'),
style: 'cancel',
},
{
- text: 'Reveal Spoilers',
+ text: t('metadata.reveal_spoilers'),
style: 'destructive',
onPress: () => {
setRevealedSpoilers(prev => new Set([...prev, comment.id.toString()]));
@@ -744,7 +744,7 @@ const MetadataScreen: React.FC = () => {
},
]
);
- }, []);
+ }, [t]);
// Source switching removed
@@ -1025,7 +1025,7 @@ const MetadataScreen: React.FC = () => {
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16,
marginBottom: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
}
- ]}>Network
+ ]}>{t('metadata.network')}
{
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16,
marginBottom: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
}
- ]}>Production
+ ]}>{t('metadata.production')}
{
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16,
marginBottom: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
}
- ]}>Movie Details
+ ]}>{t('metadata.movie_details')}
{metadata.movieDetails.tagline && (
- Tagline
+ {t('metadata.tagline')}
"{metadata.movieDetails.tagline}"
@@ -1176,14 +1176,14 @@ const MetadataScreen: React.FC = () => {
{metadata.movieDetails.status && (
- Status
+ {t('metadata.status')}
{metadata.movieDetails.status}
)}
{metadata.movieDetails.releaseDate && (
- Release Date
+ {t('metadata.release_date')}
{new Date(metadata.movieDetails.releaseDate).toLocaleDateString('en-US', {
year: 'numeric',
@@ -1196,7 +1196,7 @@ const MetadataScreen: React.FC = () => {
{metadata.movieDetails.runtime && (
- Runtime
+ {t('metadata.runtime')}
{Math.floor(metadata.movieDetails.runtime / 60)}h {metadata.movieDetails.runtime % 60}m
@@ -1205,7 +1205,7 @@ const MetadataScreen: React.FC = () => {
{metadata.movieDetails.budget && metadata.movieDetails.budget > 0 && (
- Budget
+ {t('metadata.budget')}
${metadata.movieDetails.budget.toLocaleString()}
@@ -1214,7 +1214,7 @@ const MetadataScreen: React.FC = () => {
{metadata.movieDetails.revenue && metadata.movieDetails.revenue > 0 && (
- Revenue
+ {t('metadata.revenue')}
${metadata.movieDetails.revenue.toLocaleString()}
@@ -1223,14 +1223,14 @@ const MetadataScreen: React.FC = () => {
{metadata.movieDetails.originCountry && metadata.movieDetails.originCountry.length > 0 && (
- Origin Country
+ {t('metadata.origin_country')}
{metadata.movieDetails.originCountry.join(', ')}
)}
{metadata.movieDetails.originalLanguage && (
- Original Language
+ {t('metadata.original_language')}
{metadata.movieDetails.originalLanguage.toUpperCase()}
)}
@@ -1248,7 +1248,7 @@ const MetadataScreen: React.FC = () => {
title: metadata.name || 'Gallery'
})}
>
- Backdrop Gallery
+ {t('metadata.backdrop_gallery')}
@@ -1294,18 +1294,18 @@ const MetadataScreen: React.FC = () => {
fontSize: isTV ? 20 : isLargeTablet ? 18 : isTablet ? 17 : 16,
marginBottom: isTV ? 16 : isLargeTablet ? 14 : isTablet ? 12 : 12
}
- ]}>Show Details
+ ]}>{t('metadata.show_details')}
{metadata.tvDetails.status && (
- Status
+ {t('metadata.status')}
{metadata.tvDetails.status}
)}
{metadata.tvDetails.firstAirDate && (
- First Air Date
+ {t('metadata.first_air_date')}
{new Date(metadata.tvDetails.firstAirDate).toLocaleDateString('en-US', {
year: 'numeric',
@@ -1318,7 +1318,7 @@ const MetadataScreen: React.FC = () => {
{metadata.tvDetails.lastAirDate && (
- Last Air Date
+ {t('metadata.last_air_date')}
{new Date(metadata.tvDetails.lastAirDate).toLocaleDateString('en-US', {
year: 'numeric',
@@ -1331,21 +1331,21 @@ const MetadataScreen: React.FC = () => {
{metadata.tvDetails.numberOfSeasons && (
- Seasons
+ {t('metadata.seasons')}
{metadata.tvDetails.numberOfSeasons}
)}
{metadata.tvDetails.numberOfEpisodes && (
- Total Episodes
+ {t('metadata.total_episodes')}
{metadata.tvDetails.numberOfEpisodes}
)}
{metadata.tvDetails.episodeRunTime && metadata.tvDetails.episodeRunTime.length > 0 && (
- Episode Runtime
+ {t('metadata.episode_runtime')}
{metadata.tvDetails.episodeRunTime.join(' - ')} min
@@ -1354,21 +1354,21 @@ const MetadataScreen: React.FC = () => {
{metadata.tvDetails.originCountry && metadata.tvDetails.originCountry.length > 0 && (
- Origin Country
+ {t('metadata.origin_country')}
{metadata.tvDetails.originCountry.join(', ')}
)}
{metadata.tvDetails.originalLanguage && (
- Original Language
+ {t('metadata.original_language')}
{metadata.tvDetails.originalLanguage.toUpperCase()}
)}
{metadata.tvDetails.createdBy && metadata.tvDetails.createdBy.length > 0 && (
- Created By
+ {t('metadata.created_by')}
{metadata.tvDetails.createdBy.map(creator => creator.name).join(', ')}
@@ -1388,7 +1388,7 @@ const MetadataScreen: React.FC = () => {
title: metadata.name || 'Gallery'
})}
>
- Backdrop Gallery
+ {t('metadata.backdrop_gallery')}