updated remaining contents for localization

This commit is contained in:
tapframe 2026-01-07 00:05:02 +05:30
parent 9924d26ff6
commit bbdd4c0504
7 changed files with 215 additions and 106 deletions

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { import {
View, View,
Text, Text,
@ -70,6 +71,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
onClose, onClose,
castMember, castMember,
}) => { }) => {
const { t } = useTranslation();
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const navigation = useNavigation<NavigationProp<RootStackParamList>>(); const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const [personDetails, setPersonDetails] = useState<PersonDetails | null>(null); const [personDetails, setPersonDetails] = useState<PersonDetails | null>(null);
@ -82,14 +84,14 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
if (visible && castMember) { if (visible && castMember) {
modalOpacity.value = withTiming(1, { duration: 250 }); modalOpacity.value = withTiming(1, { duration: 250 });
modalScale.value = withSpring(1, { damping: 20, stiffness: 200 }); modalScale.value = withSpring(1, { damping: 20, stiffness: 200 });
if (!hasFetched || personDetails?.id !== castMember.id) { if (!hasFetched || personDetails?.id !== castMember.id) {
fetchPersonDetails(); fetchPersonDetails();
} }
} else { } else {
modalOpacity.value = withTiming(0, { duration: 200 }); modalOpacity.value = withTiming(0, { duration: 200 });
modalScale.value = withTiming(0.9, { duration: 200 }); modalScale.value = withTiming(0.9, { duration: 200 });
if (!visible) { if (!visible) {
setHasFetched(false); setHasFetched(false);
setPersonDetails(null); setPersonDetails(null);
@ -99,7 +101,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
const fetchPersonDetails = async () => { const fetchPersonDetails = async () => {
if (!castMember || loading) return; if (!castMember || loading) return;
setLoading(true); setLoading(true);
try { try {
const details = await tmdbService.getPersonDetails(castMember.id); const details = await tmdbService.getPersonDetails(castMember.id);
@ -150,11 +152,11 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
const birthDate = new Date(birthday); const birthDate = new Date(birthday);
let age = today.getFullYear() - birthDate.getFullYear(); let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth(); const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--; age--;
} }
return age; return age;
}; };
@ -196,8 +198,8 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
height: MODAL_HEIGHT, height: MODAL_HEIGHT,
overflow: 'hidden', overflow: 'hidden',
borderRadius: isTablet ? 32 : 24, borderRadius: isTablet ? 32 : 24,
backgroundColor: Platform.OS === 'android' backgroundColor: Platform.OS === 'android'
? 'rgba(20, 20, 20, 0.95)' ? 'rgba(20, 20, 20, 0.95)'
: 'transparent', : 'transparent',
}, },
modalStyle, modalStyle,
@ -280,7 +282,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
</View> </View>
)} )}
</View> </View>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<Text style={{ <Text style={{
color: '#fff', color: '#fff',
@ -296,7 +298,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
fontSize: isTablet ? 14 : 13, fontSize: isTablet ? 14 : 13,
fontWeight: '500', fontWeight: '500',
}} numberOfLines={2}> }} numberOfLines={2}>
as {castMember.character} {t('cast.as_character', { character: castMember.character })}
</Text> </Text>
)} )}
</View> </View>
@ -336,7 +338,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
fontSize: 14, fontSize: 14,
marginTop: 12, marginTop: 12,
}}> }}>
Loading details... {t('cast.loading_details')}
</Text> </Text>
</View> </View>
) : ( ) : (
@ -352,8 +354,8 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
borderColor: 'rgba(255, 255, 255, 0.06)', borderColor: 'rgba(255, 255, 255, 0.06)',
}}> }}>
{personDetails?.birthday && ( {personDetails?.birthday && (
<View style={{ <View style={{
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: personDetails?.place_of_birth ? 10 : 0 marginBottom: personDetails?.place_of_birth ? 10 : 0
}}> }}>
@ -369,7 +371,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
fontSize: 13, fontSize: 13,
fontWeight: '500', fontWeight: '500',
}}> }}>
{calculateAge(personDetails.birthday)} years old {t('cast.years_old', { age: calculateAge(personDetails.birthday) })}
</Text> </Text>
</View> </View>
)} )}
@ -389,7 +391,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
fontWeight: '500', fontWeight: '500',
flex: 1, flex: 1,
}}> }}>
Born in {personDetails.place_of_birth} {t('cast.born_in', { place: personDetails.place_of_birth })}
</Text> </Text>
</View> </View>
)} )}
@ -420,7 +422,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
fontWeight: '600', fontWeight: '600',
letterSpacing: 0.3, letterSpacing: 0.3,
}}> }}>
View Filmography {t('cast.view_filmography')}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -454,7 +456,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
textTransform: 'uppercase', textTransform: 'uppercase',
letterSpacing: 0.5, letterSpacing: 0.5,
}}> }}>
Also Known As {t('cast.also_known_as')}
</Text> </Text>
<Text style={{ <Text style={{
color: 'rgba(255, 255, 255, 0.7)', color: 'rgba(255, 255, 255, 0.7)',
@ -480,7 +482,7 @@ export const CastDetailsModal: React.FC<CastDetailsModalProps> = ({
textAlign: 'center', textAlign: 'center',
fontWeight: '500', fontWeight: '500',
}}> }}>
No additional information available {t('cast.no_info_available')}
</Text> </Text>
</View> </View>
)} )}

View file

@ -110,7 +110,7 @@
"try_different": "جرب تصنيفاً أو كتالوجاً مختلفاً", "try_different": "جرب تصنيفاً أو كتالوجاً مختلفاً",
"select_catalog_desc": "اختر كتالوجاً للاكتشاف", "select_catalog_desc": "اختر كتالوجاً للاكتشاف",
"tap_catalog_desc": "اضغط على الكتالوج أعلاه للبدء", "tap_catalog_desc": "اضغط على الكتالوج أعلاه للبدء",
"search_placeholder": "ابحث عن أفلام، مسلسلات...", "placeholder": "ابحث عن أفلام، مسلسلات...",
"keep_typing": "استمر في الكتابة...", "keep_typing": "استمر في الكتابة...",
"type_characters": "اكتب حرفين على الأقل للبحث", "type_characters": "اكتب حرفين على الأقل للبحث",
"no_results": "لم يتم العثور على نتائج", "no_results": "لم يتم العثور على نتائج",
@ -279,7 +279,28 @@
"born_in": "وُلد في {{place}}", "born_in": "وُلد في {{place}}",
"filmography": "قائمة الأفلام", "filmography": "قائمة الأفلام",
"also_known_as": "يُعرف أيضاً بـ", "also_known_as": "يُعرف أيضاً بـ",
"no_info_available": "لا توجد معلومات إضافية متاحة" "no_info_available": "لا توجد معلومات إضافية متاحة",
"as_character": "as {{character}}",
"loading_details": "Loading details...",
"years_old": "{{age}} years old",
"view_filmography": "View Filmography",
"filter": "Filter",
"sort_by": "Sort By",
"sort_popular": "Popular",
"sort_latest": "Latest",
"sort_upcoming": "Upcoming",
"upcoming_badge": "UPCOMING",
"coming_soon": "Coming Soon",
"filmography_count": "Filmography • {{count}} titles",
"loading_filmography": "Loading filmography...",
"load_more_remaining": "Load More ({{count}} remaining)",
"alert_error_title": "Error",
"alert_error_message": "Unable to load \"{{title}}\". Please try again later.",
"alert_ok": "OK",
"no_upcoming": "No upcoming releases available for this actor",
"no_content": "No content available for this actor",
"no_movies": "No movies available for this actor",
"no_tv": "No TV shows available for this actor"
}, },
"comments": { "comments": {
"title": "تعليقات Trakt", "title": "تعليقات Trakt",
@ -1124,7 +1145,7 @@
"clear_cache_desc": "سيؤدي هذا لإزالة رابط المستودع المحفوظ ومسح كل بيانات البلاجنز المخزنة مؤقتاً. ستحتاج لإعادة إدخال رابط المستودع.", "clear_cache_desc": "سيؤدي هذا لإزالة رابط المستودع المحفوظ ومسح كل بيانات البلاجنز المخزنة مؤقتاً. ستحتاج لإعادة إدخال رابط المستودع.",
"add_new_repo": "إضافة مستودع جديد", "add_new_repo": "إضافة مستودع جديد",
"available_plugins": "البلاجنز المتاحة ({{count}})", "available_plugins": "البلاجنز المتاحة ({{count}})",
"search_placeholder": "البحث في البلاجنز...", "placeholder": "البحث في البلاجنز...",
"all": "الكل", "all": "الكل",
"filter_all": "كل الأنواع", "filter_all": "كل الأنواع",
"filter_movies": "أفلام", "filter_movies": "أفلام",
@ -1171,4 +1192,4 @@
"cancel": "إلغاء", "cancel": "إلغاء",
"add": "إضافة" "add": "إضافة"
} }
} }

View file

@ -110,7 +110,7 @@
"try_different": "Try a different genre or catalog", "try_different": "Try a different genre or catalog",
"select_catalog_desc": "Select a catalog to discover", "select_catalog_desc": "Select a catalog to discover",
"tap_catalog_desc": "Tap the catalog chip above to get started", "tap_catalog_desc": "Tap the catalog chip above to get started",
"search_placeholder": "Search movies, shows...", "placeholder": "Search movies, shows...",
"keep_typing": "Keep typing...", "keep_typing": "Keep typing...",
"type_characters": "Type at least 2 characters to search", "type_characters": "Type at least 2 characters to search",
"no_results": "No results found", "no_results": "No results found",
@ -279,7 +279,28 @@
"born_in": "Born in {{place}}", "born_in": "Born in {{place}}",
"filmography": "Filmography", "filmography": "Filmography",
"also_known_as": "Also Known As", "also_known_as": "Also Known As",
"no_info_available": "No additional information available" "no_info_available": "No additional information available",
"as_character": "as {{character}}",
"loading_details": "Loading details...",
"years_old": "{{age}} years old",
"view_filmography": "View Filmography",
"filter": "Filter",
"sort_by": "Sort By",
"sort_popular": "Popular",
"sort_latest": "Latest",
"sort_upcoming": "Upcoming",
"upcoming_badge": "UPCOMING",
"coming_soon": "Coming Soon",
"filmography_count": "Filmography • {{count}} titles",
"loading_filmography": "Loading filmography...",
"load_more_remaining": "Load More ({{count}} remaining)",
"alert_error_title": "Error",
"alert_error_message": "Unable to load \"{{title}}\". Please try again later.",
"alert_ok": "OK",
"no_upcoming": "No upcoming releases available for this actor",
"no_content": "No content available for this actor",
"no_movies": "No movies available for this actor",
"no_tv": "No TV shows available for this actor"
}, },
"comments": { "comments": {
"title": "Trakt Comments", "title": "Trakt Comments",
@ -1124,7 +1145,7 @@
"clear_cache_desc": "This will remove the saved repository URL and clear all cached plugin data. You will need to re-enter your repository URL.", "clear_cache_desc": "This will remove the saved repository URL and clear all cached plugin data. You will need to re-enter your repository URL.",
"add_new_repo": "Add New Repository", "add_new_repo": "Add New Repository",
"available_plugins": "Available Plugins ({{count}})", "available_plugins": "Available Plugins ({{count}})",
"search_placeholder": "Search plugins...", "placeholder": "Search plugins...",
"all": "All", "all": "All",
"filter_all": "All Types", "filter_all": "All Types",
"filter_movies": "Movies", "filter_movies": "Movies",

View file

@ -110,7 +110,7 @@
"try_different": "Prueba con un género o catálogo diferente", "try_different": "Prueba con un género o catálogo diferente",
"select_catalog_desc": "Selecciona un catálogo para descubrir", "select_catalog_desc": "Selecciona un catálogo para descubrir",
"tap_catalog_desc": "Toca el catálogo arriba para empezar", "tap_catalog_desc": "Toca el catálogo arriba para empezar",
"search_placeholder": "Buscar películas, series...", "placeholder": "Buscar películas, series...",
"keep_typing": "Sigue escribiendo...", "keep_typing": "Sigue escribiendo...",
"type_characters": "Escribe al menos 2 caracteres para buscar", "type_characters": "Escribe al menos 2 caracteres para buscar",
"no_results": "No se encontraron resultados", "no_results": "No se encontraron resultados",
@ -279,7 +279,28 @@
"born_in": "Nacido/a en {{place}}", "born_in": "Nacido/a en {{place}}",
"filmography": "Filmografía", "filmography": "Filmografía",
"also_known_as": "También conocido/a como", "also_known_as": "También conocido/a como",
"no_info_available": "No hay información adicional disponible" "no_info_available": "No additional information available",
"as_character": "as {{character}}",
"loading_details": "Loading details...",
"years_old": "{{age}} years old",
"view_filmography": "View Filmography",
"filter": "Filter",
"sort_by": "Sort By",
"sort_popular": "Popular",
"sort_latest": "Latest",
"sort_upcoming": "Upcoming",
"upcoming_badge": "UPCOMING",
"coming_soon": "Coming Soon",
"filmography_count": "Filmography • {{count}} titles",
"loading_filmography": "Loading filmography...",
"load_more_remaining": "Load More ({{count}} remaining)",
"alert_error_title": "Error",
"alert_error_message": "Unable to load \"{{title}}\". Please try again later.",
"alert_ok": "OK",
"no_upcoming": "No upcoming releases available for this actor",
"no_content": "No content available for this actor",
"no_movies": "No movies available for this actor",
"no_tv": "No TV shows available for this actor"
}, },
"comments": { "comments": {
"title": "Comentarios de Trakt", "title": "Comentarios de Trakt",
@ -1124,7 +1145,7 @@
"clear_cache_desc": "Esto eliminará la URL guardada y los datos en caché. Tendrás que introducir de nuevo la URL del repositorio.", "clear_cache_desc": "Esto eliminará la URL guardada y los datos en caché. Tendrás que introducir de nuevo la URL del repositorio.",
"add_new_repo": "Añadir nuevo repositorio", "add_new_repo": "Añadir nuevo repositorio",
"available_plugins": "Plugins disponibles ({{count}})", "available_plugins": "Plugins disponibles ({{count}})",
"search_placeholder": "Buscar plugins...", "placeholder": "Buscar plugins...",
"all": "Todo", "all": "Todo",
"filter_all": "Todos los tipos", "filter_all": "Todos los tipos",
"filter_movies": "Películas", "filter_movies": "Películas",
@ -1171,4 +1192,4 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"add": "Añadir" "add": "Añadir"
} }
} }

View file

@ -110,7 +110,7 @@
"try_different": "Essayez un genre ou un catalogue différent", "try_different": "Essayez un genre ou un catalogue différent",
"select_catalog_desc": "Sélectionnez un catalogue à découvrir", "select_catalog_desc": "Sélectionnez un catalogue à découvrir",
"tap_catalog_desc": "Appuyez sur le jeton de catalogue ci-dessus pour commencer", "tap_catalog_desc": "Appuyez sur le jeton de catalogue ci-dessus pour commencer",
"search_placeholder": "Rechercher des films, séries...", "placeholder": "Rechercher des films, séries...",
"keep_typing": "Continuez à taper...", "keep_typing": "Continuez à taper...",
"type_characters": "Tapez au moins 2 caractères pour rechercher", "type_characters": "Tapez au moins 2 caractères pour rechercher",
"no_results": "Aucun résultat trouvé", "no_results": "Aucun résultat trouvé",
@ -278,7 +278,28 @@
"personal_info": "Infos personnelles", "personal_info": "Infos personnelles",
"born_in": "Né à {{place}}", "born_in": "Né à {{place}}",
"filmography": "Filmographie", "filmography": "Filmographie",
"also_known_as": "Aussi connu sous le nom de", "also_known_as": "Aussi connu(e) sous le nom de",
"as_character": "as {{character}}",
"loading_details": "Loading details...",
"years_old": "{{age}} years old",
"view_filmography": "View Filmography",
"filter": "Filter",
"sort_by": "Sort By",
"sort_popular": "Popular",
"sort_latest": "Latest",
"sort_upcoming": "Upcoming",
"upcoming_badge": "UPCOMING",
"coming_soon": "Coming Soon",
"filmography_count": "Filmography • {{count}} titles",
"loading_filmography": "Loading filmography...",
"load_more_remaining": "Load More ({{count}} remaining)",
"alert_error_title": "Error",
"alert_error_message": "Unable to load \"{{title}}\". Please try again later.",
"alert_ok": "OK",
"no_upcoming": "No upcoming releases available for this actor",
"no_content": "No content available for this actor",
"no_movies": "No movies available for this actor",
"no_tv": "No TV shows available for this actor",
"no_info_available": "Aucune information supplémentaire disponible" "no_info_available": "Aucune information supplémentaire disponible"
}, },
"comments": { "comments": {
@ -1124,7 +1145,7 @@
"clear_cache_desc": "Cela supprimera l'URL du dépôt enregistrée et effacera toutes les données de plugin mises en cache. Vous devrez ressaisir votre URL de dépôt.", "clear_cache_desc": "Cela supprimera l'URL du dépôt enregistrée et effacera toutes les données de plugin mises en cache. Vous devrez ressaisir votre URL de dépôt.",
"add_new_repo": "Ajouter un nouveau dépôt", "add_new_repo": "Ajouter un nouveau dépôt",
"available_plugins": "Plugins disponibles ({{count}})", "available_plugins": "Plugins disponibles ({{count}})",
"search_placeholder": "Rechercher des plugins...", "placeholder": "Rechercher des plugins...",
"all": "Tout", "all": "Tout",
"filter_all": "Tous les types", "filter_all": "Tous les types",
"filter_movies": "Films", "filter_movies": "Films",
@ -1171,4 +1192,4 @@
"cancel": "Annuler", "cancel": "Annuler",
"add": "Ajouter" "add": "Ajouter"
} }
} }

View file

@ -110,7 +110,7 @@
"try_different": "Tente um gênero ou catálogo diferente", "try_different": "Tente um gênero ou catálogo diferente",
"select_catalog_desc": "Selecione um catálogo para descobrir", "select_catalog_desc": "Selecione um catálogo para descobrir",
"tap_catalog_desc": "Toque no botão de catálogo acima para começar", "tap_catalog_desc": "Toque no botão de catálogo acima para começar",
"search_placeholder": "Buscar filmes, séries...", "placeholder": "Buscar filmes, séries...",
"keep_typing": "Continue digitando...", "keep_typing": "Continue digitando...",
"type_characters": "Digite pelo menos 2 caracteres para buscar", "type_characters": "Digite pelo menos 2 caracteres para buscar",
"no_results": "Nenhum resultado encontrado", "no_results": "Nenhum resultado encontrado",
@ -278,7 +278,28 @@
"personal_info": "Informações Pessoais", "personal_info": "Informações Pessoais",
"born_in": "Nascido em {{place}}", "born_in": "Nascido em {{place}}",
"filmography": "Filmografia", "filmography": "Filmografia",
"also_known_as": "Também Conhecido Como", "also_known_as": "Também conhecido(a) como",
"as_character": "como {{character}}",
"loading_details": "Carregando detalhes...",
"years_old": "{{age}} anos",
"view_filmography": "Ver Filmografia",
"filter": "Filtrar",
"sort_by": "Ordenar Por",
"sort_popular": "Popular",
"sort_latest": "Mais Recente",
"sort_upcoming": "Próximos Lançamentos",
"upcoming_badge": "EM BREVE",
"coming_soon": "Em Breve",
"filmography_count": "Filmografia • {{count}} títulos",
"loading_filmography": "Carregando filmografia...",
"load_more_remaining": "Carregar Mais ({{count}} restantes)",
"alert_error_title": "Erro",
"alert_error_message": "Não foi possível carregar \"{{title}}\". Por favor, tente novamente mais tarde.",
"alert_ok": "OK",
"no_upcoming": "Nenhum lançamento futuro disponível para este ator",
"no_content": "Nenhum conteúdo disponível para este ator",
"no_movies": "Nenhum filme disponível para este ator",
"no_tv": "Nenhuma série disponível para este ator",
"no_info_available": "Nenhuma informação adicional disponível" "no_info_available": "Nenhuma informação adicional disponível"
}, },
"comments": { "comments": {
@ -1090,7 +1111,7 @@
"clear_cache_desc": "Isso removerá a URL do repositório salvo e limpará todos os dados de plugins armazenados em cache. Você precisará digitar a URL do repositório novamente.", "clear_cache_desc": "Isso removerá a URL do repositório salvo e limpará todos os dados de plugins armazenados em cache. Você precisará digitar a URL do repositório novamente.",
"add_new_repo": "Adicionar Novo Repositório", "add_new_repo": "Adicionar Novo Repositório",
"available_plugins": "Plugins Disponíveis ({{count}})", "available_plugins": "Plugins Disponíveis ({{count}})",
"search_placeholder": "Pesquisar plugins...", "placeholder": "Pesquisar plugins...",
"all": "Todos", "all": "Todos",
"filter_all": "Todos Tipos", "filter_all": "Todos Tipos",
"filter_movies": "Filmes", "filter_movies": "Filmes",

View file

@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { import {
View, View,
Text, Text,
@ -59,6 +60,7 @@ type CastMoviesScreenRouteProp = RouteProp<RootStackParamList, 'CastMovies'>;
const CastMoviesScreen: React.FC = () => { const CastMoviesScreen: React.FC = () => {
const { currentTheme } = useTheme(); const { currentTheme } = useTheme();
const { t } = useTranslation();
const navigation = useNavigation<NavigationProp<RootStackParamList>>(); const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const route = useRoute<CastMoviesScreenRouteProp>(); const route = useRoute<CastMoviesScreenRouteProp>();
const { castMember } = route.params; const { castMember } = route.params;
@ -89,27 +91,27 @@ const CastMoviesScreen: React.FC = () => {
const fetchCastCredits = async () => { const fetchCastCredits = async () => {
if (!castMember) return; if (!castMember) return;
setLoading(true); setLoading(true);
try { try {
const credits = await tmdbService.getPersonCombinedCredits(castMember.id); const credits = await tmdbService.getPersonCombinedCredits(castMember.id);
if (credits && credits.cast) { if (credits && credits.cast) {
const currentDate = new Date(); const currentDate = new Date();
// Combine cast roles with enhanced data, excluding talk shows and variety shows // Combine cast roles with enhanced data, excluding talk shows and variety shows
const allCredits = credits.cast const allCredits = credits.cast
.filter((item: any) => { .filter((item: any) => {
// Filter out talk shows, variety shows, and ensure we have required data // Filter out talk shows, variety shows, and ensure we have required data
const hasPoster = item.poster_path; const hasPoster = item.poster_path;
const hasReleaseDate = item.release_date || item.first_air_date; const hasReleaseDate = item.release_date || item.first_air_date;
if (!hasPoster || !hasReleaseDate) return false; if (!hasPoster || !hasReleaseDate) return false;
// Enhanced talk show filtering // Enhanced talk show filtering
const title = (item.title || item.name || '').toLowerCase(); const title = (item.title || item.name || '').toLowerCase();
const overview = (item.overview || '').toLowerCase(); const overview = (item.overview || '').toLowerCase();
// List of common talk show and variety show keywords // List of common talk show and variety show keywords
const talkShowKeywords = [ const talkShowKeywords = [
'talk', 'show', 'late night', 'tonight show', 'jimmy fallon', 'snl', 'saturday night live', 'talk', 'show', 'late night', 'tonight show', 'jimmy fallon', 'snl', 'saturday night live',
@ -120,18 +122,18 @@ const CastMoviesScreen: React.FC = () => {
'red carpet', 'premiere', 'after party', 'behind the scenes', 'making of', 'documentary', 'red carpet', 'premiere', 'after party', 'behind the scenes', 'making of', 'documentary',
'special', 'concert', 'live performance', 'mtv', 'vh1', 'bet', 'comedy', 'roast' 'special', 'concert', 'live performance', 'mtv', 'vh1', 'bet', 'comedy', 'roast'
]; ];
// Check if any keyword matches // Check if any keyword matches
const isTalkShow = talkShowKeywords.some(keyword => const isTalkShow = talkShowKeywords.some(keyword =>
title.includes(keyword) || overview.includes(keyword) title.includes(keyword) || overview.includes(keyword)
); );
return !isTalkShow; return !isTalkShow;
}) })
.map((item: any) => { .map((item: any) => {
const releaseDate = new Date(item.release_date || item.first_air_date); const releaseDate = new Date(item.release_date || item.first_air_date);
const isUpcoming = releaseDate > currentDate; const isUpcoming = releaseDate > currentDate;
return { return {
id: item.id, id: item.id,
title: item.title || item.name, title: item.title || item.name,
@ -144,7 +146,7 @@ const CastMoviesScreen: React.FC = () => {
isUpcoming, isUpcoming,
}; };
}); });
setMovies(allCredits); setMovies(allCredits);
} }
} catch (error) { } catch (error) {
@ -223,41 +225,41 @@ const CastMoviesScreen: React.FC = () => {
isUpcoming: movie.isUpcoming isUpcoming: movie.isUpcoming
}); });
} }
try { try {
if (__DEV__) console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString()); if (__DEV__) console.log('Attempting to get Stremio ID for:', movie.media_type, movie.id.toString());
// Get Stremio ID using catalogService // Get Stremio ID using catalogService
const stremioId = await catalogService.getStremioId(movie.media_type, movie.id.toString()); const stremioId = await catalogService.getStremioId(movie.media_type, movie.id.toString());
if (__DEV__) console.log('Stremio ID result:', stremioId); if (__DEV__) console.log('Stremio ID result:', stremioId);
if (stremioId) { if (stremioId) {
if (__DEV__) console.log('Successfully found Stremio ID, navigating to Metadata with:', { if (__DEV__) console.log('Successfully found Stremio ID, navigating to Metadata with:', {
id: stremioId, id: stremioId,
type: movie.media_type type: movie.media_type
}); });
// Convert TMDB media type to Stremio media type // Convert TMDB media type to Stremio media type
const stremioType = movie.media_type === 'tv' ? 'series' : movie.media_type; const stremioType = movie.media_type === 'tv' ? 'series' : movie.media_type;
if (__DEV__) console.log('Navigating with Stremio type conversion:', { if (__DEV__) console.log('Navigating with Stremio type conversion:', {
originalType: movie.media_type, originalType: movie.media_type,
stremioType: stremioType, stremioType: stremioType,
id: stremioId id: stremioId
}); });
navigation.dispatch( navigation.dispatch(
StackActions.push('Metadata', { StackActions.push('Metadata', {
id: stremioId, id: stremioId,
type: stremioType type: stremioType
}) })
); );
} else { } else {
if (__DEV__) console.warn('Stremio ID is null/undefined for movie:', movie.title); if (__DEV__) console.warn('Stremio ID is null/undefined for movie:', movie.title);
throw new Error('Could not find Stremio ID'); throw new Error('Could not find Stremio ID');
} }
} catch (error: any) { } catch (error: any) {
if (__DEV__) { if (__DEV__) {
console.error('=== Error in handleMoviePress ==='); console.error('=== Error in handleMoviePress ===');
console.error('Movie:', movie.title); console.error('Movie:', movie.title);
@ -265,9 +267,9 @@ const CastMoviesScreen: React.FC = () => {
console.error('Error message:', error.message); console.error('Error message:', error.message);
console.error('Error stack:', error.stack); console.error('Error stack:', error.stack);
} }
setAlertTitle('Error'); setAlertTitle(t('cast.alert_error_title'));
setAlertMessage(`Unable to load "${movie.title}". Please try again later.`); setAlertMessage(t('cast.alert_error_message', { title: movie.title }));
setAlertActions([{ label: 'OK', onPress: () => {} }]); setAlertActions([{ label: t('cast.alert_ok'), onPress: () => { } }]);
setAlertVisible(true); setAlertVisible(true);
} }
}; };
@ -278,7 +280,7 @@ const CastMoviesScreen: React.FC = () => {
const renderFilterButton = (filter: 'all' | 'movies' | 'tv', label: string, count: number) => { const renderFilterButton = (filter: 'all' | 'movies' | 'tv', label: string, count: number) => {
const isSelected = selectedFilter === filter; const isSelected = selectedFilter === filter;
return ( return (
<Animated.View entering={FadeIn.delay(100)}> <Animated.View entering={FadeIn.delay(100)}>
<TouchableOpacity <TouchableOpacity
@ -286,8 +288,8 @@ const CastMoviesScreen: React.FC = () => {
paddingHorizontal: 18, paddingHorizontal: 18,
paddingVertical: 10, paddingVertical: 10,
borderRadius: 25, borderRadius: 25,
backgroundColor: isSelected backgroundColor: isSelected
? currentTheme.colors.primary ? currentTheme.colors.primary
: 'rgba(255, 255, 255, 0.08)', : 'rgba(255, 255, 255, 0.08)',
marginRight: 12, marginRight: 12,
borderWidth: isSelected ? 0 : 1, borderWidth: isSelected ? 0 : 1,
@ -311,7 +313,7 @@ const CastMoviesScreen: React.FC = () => {
const renderSortButton = (sort: 'popularity' | 'latest' | 'upcoming', label: string, icon: string) => { const renderSortButton = (sort: 'popularity' | 'latest' | 'upcoming', label: string, icon: string) => {
const isSelected = sortBy === sort; const isSelected = sortBy === sort;
return ( return (
<Animated.View entering={FadeIn.delay(200)}> <Animated.View entering={FadeIn.delay(200)}>
<TouchableOpacity <TouchableOpacity
@ -319,8 +321,8 @@ const CastMoviesScreen: React.FC = () => {
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 8, paddingVertical: 8,
borderRadius: 20, borderRadius: 20,
backgroundColor: isSelected backgroundColor: isSelected
? 'rgba(255, 255, 255, 0.15)' ? 'rgba(255, 255, 255, 0.15)'
: 'transparent', : 'transparent',
marginRight: 12, marginRight: 12,
flexDirection: 'row', flexDirection: 'row',
@ -329,10 +331,10 @@ const CastMoviesScreen: React.FC = () => {
onPress={() => setSortBy(sort)} onPress={() => setSortBy(sort)}
activeOpacity={0.7} activeOpacity={0.7}
> >
<MaterialIcons <MaterialIcons
name={icon as any} name={icon as any}
size={16} size={16}
color={isSelected ? currentTheme.colors.primary : 'rgba(255, 255, 255, 0.6)'} color={isSelected ? currentTheme.colors.primary : 'rgba(255, 255, 255, 0.6)'}
style={{ marginRight: 6 }} style={{ marginRight: 6 }}
/> />
<Text style={{ <Text style={{
@ -397,7 +399,7 @@ const CastMoviesScreen: React.FC = () => {
<MaterialIcons name="movie" size={32} color="rgba(255, 255, 255, 0.2)" /> <MaterialIcons name="movie" size={32} color="rgba(255, 255, 255, 0.2)" />
</View> </View>
)} )}
{/* Upcoming indicator */} {/* Upcoming indicator */}
{item.isUpcoming && ( {item.isUpcoming && (
<View style={{ <View style={{
@ -419,7 +421,7 @@ const CastMoviesScreen: React.FC = () => {
marginLeft: 4, marginLeft: 4,
letterSpacing: 0.2, letterSpacing: 0.2,
}}> }}>
UPCOMING {t('cast.upcoming_badge')}
</Text> </Text>
</View> </View>
)} )}
@ -463,7 +465,7 @@ const CastMoviesScreen: React.FC = () => {
}} }}
/> />
</View> </View>
<View style={{ paddingHorizontal: 4, marginTop: 8 }}> <View style={{ paddingHorizontal: 4, marginTop: 8 }}>
<Text style={{ <Text style={{
color: '#fff', color: '#fff',
@ -474,7 +476,7 @@ const CastMoviesScreen: React.FC = () => {
}} numberOfLines={2}> }} numberOfLines={2}>
{`${item.title}`} {`${item.title}`}
</Text> </Text>
{item.character && ( {item.character && (
<Text style={{ <Text style={{
color: 'rgba(255, 255, 255, 0.65)', color: 'rgba(255, 255, 255, 0.65)',
@ -482,10 +484,10 @@ const CastMoviesScreen: React.FC = () => {
marginTop: 3, marginTop: 3,
fontWeight: '500', fontWeight: '500',
}} numberOfLines={1}> }} numberOfLines={1}>
{`as ${item.character}`} {t('cast.as_character', { character: item.character })}
</Text> </Text>
)} )}
<View style={{ <View style={{
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -502,7 +504,7 @@ const CastMoviesScreen: React.FC = () => {
{`${new Date(item.release_date).getFullYear()}`} {`${new Date(item.release_date).getFullYear()}`}
</Text> </Text>
)} )}
{item.isUpcoming && ( {item.isUpcoming && (
<View style={{ <View style={{
flexDirection: 'row', flexDirection: 'row',
@ -516,7 +518,7 @@ const CastMoviesScreen: React.FC = () => {
marginLeft: 2, marginLeft: 2,
letterSpacing: 0.2, letterSpacing: 0.2,
}}> }}>
Coming Soon {t('cast.coming_soon')}
</Text> </Text>
</View> </View>
)} )}
@ -538,7 +540,7 @@ const CastMoviesScreen: React.FC = () => {
[1, 0.9], [1, 0.9],
Extrapolate.CLAMP Extrapolate.CLAMP
); );
return { return {
opacity, opacity,
}; };
@ -547,7 +549,7 @@ const CastMoviesScreen: React.FC = () => {
return ( return (
<View style={{ flex: 1, backgroundColor: currentTheme.colors.darkBackground }}> <View style={{ flex: 1, backgroundColor: currentTheme.colors.darkBackground }}>
{/* Minimal Header */} {/* Minimal Header */}
<Animated.View <Animated.View
style={[ style={[
{ {
paddingTop: safeAreaTop + 16, paddingTop: safeAreaTop + 16,
@ -560,7 +562,7 @@ const CastMoviesScreen: React.FC = () => {
headerAnimatedStyle headerAnimatedStyle
]} ]}
> >
<Animated.View <Animated.View
entering={SlideInDown.delay(100)} entering={SlideInDown.delay(100)}
style={{ flexDirection: 'row', alignItems: 'center' }} style={{ flexDirection: 'row', alignItems: 'center' }}
> >
@ -579,7 +581,7 @@ const CastMoviesScreen: React.FC = () => {
> >
<MaterialIcons name="arrow-back" size={20} color="rgba(255, 255, 255, 0.9)" /> <MaterialIcons name="arrow-back" size={20} color="rgba(255, 255, 255, 0.9)" />
</TouchableOpacity> </TouchableOpacity>
<View style={{ <View style={{
width: 44, width: 44,
height: 44, height: 44,
@ -613,7 +615,7 @@ const CastMoviesScreen: React.FC = () => {
</View> </View>
)} )}
</View> </View>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
<Text style={{ <Text style={{
color: '#fff', color: '#fff',
@ -630,7 +632,7 @@ const CastMoviesScreen: React.FC = () => {
fontWeight: '500', fontWeight: '500',
letterSpacing: 0.2, letterSpacing: 0.2,
}}> }}>
{`Filmography • ${movies.length} titles`} {t('cast.filmography_count', { count: movies.length })}
</Text> </Text>
</View> </View>
</Animated.View> </Animated.View>
@ -652,16 +654,16 @@ const CastMoviesScreen: React.FC = () => {
letterSpacing: 0.5, letterSpacing: 0.5,
textTransform: 'uppercase', textTransform: 'uppercase',
}}> }}>
Filter {t('cast.filter')}
</Text> </Text>
<ScrollView <ScrollView
horizontal horizontal
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingRight: 20 }} contentContainerStyle={{ paddingRight: 20 }}
> >
{renderFilterButton('all', 'All', movies.length)} {renderFilterButton('all', t('catalog.all'), movies.length)}
{renderFilterButton('movies', 'Movies', movieCount)} {renderFilterButton('movies', t('catalog.movies'), movieCount)}
{renderFilterButton('tv', 'TV Shows', tvCount)} {renderFilterButton('tv', t('catalog.tv_shows'), tvCount)}
</ScrollView> </ScrollView>
</View> </View>
@ -675,16 +677,16 @@ const CastMoviesScreen: React.FC = () => {
letterSpacing: 0.5, letterSpacing: 0.5,
textTransform: 'uppercase', textTransform: 'uppercase',
}}> }}>
Sort By {t('cast.sort_by')}
</Text> </Text>
<ScrollView <ScrollView
horizontal horizontal
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
contentContainerStyle={{ paddingRight: 20 }} contentContainerStyle={{ paddingRight: 20 }}
> >
{renderSortButton('popularity', 'Popular', 'trending-up')} {renderSortButton('popularity', t('cast.sort_popular'), 'trending-up')}
{renderSortButton('latest', 'Latest', 'schedule')} {renderSortButton('latest', t('cast.sort_latest'), 'schedule')}
{renderSortButton('upcoming', 'Upcoming', 'event')} {renderSortButton('upcoming', t('cast.sort_upcoming'), 'event')}
</ScrollView> </ScrollView>
</View> </View>
</View> </View>
@ -703,7 +705,7 @@ const CastMoviesScreen: React.FC = () => {
marginTop: 12, marginTop: 12,
fontWeight: '500', fontWeight: '500',
}}> }}>
Loading filmography... {t('cast.loading_filmography')}
</Text> </Text>
</View> </View>
) : ( ) : (
@ -755,7 +757,7 @@ const CastMoviesScreen: React.FC = () => {
fontSize: 14, fontSize: 14,
fontWeight: '600', fontWeight: '600',
}}> }}>
{`Load More (${filteredAndSortedMovies.length - displayLimit} remaining)`} {t('cast.load_more_remaining', { count: filteredAndSortedMovies.length - displayLimit })}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
@ -763,7 +765,7 @@ const CastMoviesScreen: React.FC = () => {
) : null ) : null
} }
ListEmptyComponent={ ListEmptyComponent={
<Animated.View <Animated.View
entering={FadeIn.delay(400)} entering={FadeIn.delay(400)}
style={{ style={{
alignItems: 'center', alignItems: 'center',
@ -790,7 +792,7 @@ const CastMoviesScreen: React.FC = () => {
marginBottom: 8, marginBottom: 8,
textAlign: 'center', textAlign: 'center',
}}> }}>
No Content Found {t('catalog.no_content_found')}
</Text> </Text>
<Text style={{ <Text style={{
color: 'rgba(255, 255, 255, 0.5)', color: 'rgba(255, 255, 255, 0.5)',
@ -799,13 +801,13 @@ const CastMoviesScreen: React.FC = () => {
lineHeight: 20, lineHeight: 20,
fontWeight: '500', fontWeight: '500',
}}> }}>
{sortBy === 'upcoming' {sortBy === 'upcoming'
? 'No upcoming releases available for this actor' ? t('cast.no_upcoming')
: selectedFilter === 'all' : selectedFilter === 'all'
? 'No content available for this actor' ? t('cast.no_content')
: selectedFilter === 'movies' : selectedFilter === 'movies'
? 'No movies available for this actor' ? t('cast.no_movies')
: 'No TV shows available for this actor' : t('cast.no_tv')
} }
</Text> </Text>
</Animated.View> </Animated.View>