Merge branch 'main' into saimuelbr/main

This commit is contained in:
tapframe 2026-02-25 00:31:17 +05:30
commit e6faa7c59f
30 changed files with 21611 additions and 20166 deletions

View file

@ -768,7 +768,17 @@ const AndroidVideoPlayer: React.FC = () => {
onProgress={handleProgress} onProgress={handleProgress}
onSeek={(data) => { onSeek={(data) => {
playerState.isSeeking.current = false; playerState.isSeeking.current = false;
if (data.currentTime) traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true); if (data.currentTime) {
if (id && type && playerState.duration > 0) {
void storageService.setWatchProgress(id, type, {
currentTime: data.currentTime,
duration: playerState.duration,
lastUpdated: Date.now(),
addonId: currentStreamProvider
}, episodeId);
}
traktAutosync.handleProgressUpdate(data.currentTime, playerState.duration, true);
}
}} }}
onEnd={() => { onEnd={() => {
if (modals.showEpisodeStreamsModal) return; if (modals.showEpisodeStreamsModal) return;

View file

@ -48,6 +48,7 @@ import { useTraktAutosync } from '../../hooks/useTraktAutosync';
import { useMetadata } from '../../hooks/useMetadata'; import { useMetadata } from '../../hooks/useMetadata';
import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls'; import { usePlayerGestureControls } from '../../hooks/usePlayerGestureControls';
import stremioService from '../../services/stremioService'; import stremioService from '../../services/stremioService';
import { storageService } from '../../services/storageService';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';
// Utils // Utils
@ -227,7 +228,15 @@ const KSPlayerCore: React.FC = () => {
currentTime, currentTime,
duration, duration,
isSeeking, isSeeking,
isMounted isMounted,
onSeekComplete: (timeInSeconds) => {
if (!id || !type || duration <= 0) return;
void storageService.setWatchProgress(id, type, {
currentTime: timeInSeconds,
duration,
lastUpdated: Date.now()
}, episodeId);
}
}); });
const watchProgress = useWatchProgress( const watchProgress = useWatchProgress(

View file

@ -17,6 +17,7 @@ interface PlayerControlsConfig {
duration: number; duration: number;
isSeeking: MutableRefObject<boolean>; isSeeking: MutableRefObject<boolean>;
isMounted: MutableRefObject<boolean>; isMounted: MutableRefObject<boolean>;
onSeekComplete?: (timeInSeconds: number) => void;
} }
export const usePlayerControls = (config: PlayerControlsConfig) => { export const usePlayerControls = (config: PlayerControlsConfig) => {
@ -27,7 +28,8 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
currentTime, currentTime,
duration, duration,
isSeeking, isSeeking,
isMounted isMounted,
onSeekComplete
} = config; } = config;
// iOS seeking helpers // iOS seeking helpers
@ -54,6 +56,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
// Actually perform the seek // Actually perform the seek
playerRef.current.seek(timeInSeconds); playerRef.current.seek(timeInSeconds);
onSeekComplete?.(timeInSeconds);
// Debounce the seeking state reset // Debounce the seeking state reset
seekTimeoutRef.current = setTimeout(() => { seekTimeoutRef.current = setTimeout(() => {
@ -62,7 +65,7 @@ export const usePlayerControls = (config: PlayerControlsConfig) => {
} }
}, 500); }, 500);
} }
}, [duration, paused, playerRef, isSeeking, isMounted]); }, [duration, paused, playerRef, isSeeking, isMounted, onSeekComplete]);
const skip = useCallback((seconds: number) => { const skip = useCallback((seconds: number) => {
seekToTime(currentTime + seconds); seekToTime(currentTime + seconds);

View file

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native'; import { AppState } from 'react-native';
import { storageService } from '../../../services/storageService'; import { storageService } from '../../../services/storageService';
import { logger } from '../../../utils/logger'; import { logger } from '../../../utils/logger';
import { useSettings } from '../../../hooks/useSettings'; import { useSettings } from '../../../hooks/useSettings';
@ -19,10 +19,9 @@ export const useWatchProgress = (
const [savedDuration, setSavedDuration] = useState<number | null>(null); const [savedDuration, setSavedDuration] = useState<number | null>(null);
const [initialPosition, setInitialPosition] = useState<number | null>(null); const [initialPosition, setInitialPosition] = useState<number | null>(null);
const [showResumeOverlay, setShowResumeOverlay] = useState(false); const [showResumeOverlay, setShowResumeOverlay] = useState(false);
const [progressSaveInterval, setProgressSaveInterval] = useState<NodeJS.Timeout | null>(null);
const { settings: appSettings } = useSettings(); const { settings: appSettings } = useSettings();
const initialSeekTargetRef = useRef<number | null>(null); const initialSeekTargetRef = useRef<number | null>(null);
const wasPausedRef = useRef<boolean>(paused);
// Values refs for unmount cleanup // Values refs for unmount cleanup
const currentTimeRef = useRef(currentTime); const currentTimeRef = useRef(currentTime);
@ -126,22 +125,16 @@ export const useWatchProgress = (
} }
}; };
// Save Interval
useEffect(() => { useEffect(() => {
if (id && type && !paused && duration > 0) { if (wasPausedRef.current !== paused) {
if (progressSaveInterval) clearInterval(progressSaveInterval); const becamePaused = paused;
wasPausedRef.current = paused;
const interval = setInterval(() => { if (becamePaused) {
saveWatchProgress(); void saveWatchProgress();
}, 10000); }
setProgressSaveInterval(interval);
return () => {
clearInterval(interval);
setProgressSaveInterval(null);
};
} }
}, [id, type, paused, currentTime, duration]); }, [paused]);
// Unmount Save - deferred to allow navigation to complete first // Unmount Save - deferred to allow navigation to complete first
useEffect(() => { useEffect(() => {

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "سيؤدي هذا إلى إعادة تعيين كل الإعدادات ومسح كل البيانات المخزنة مؤقتاً. هل أنت متأكد؟", "clear_data_desc": "سيؤدي هذا إلى إعادة تعيين كل الإعدادات ومسح كل البيانات المخزنة مؤقتاً. هل أنت متأكد؟",
"app_updates": "تحديثات التطبيق", "app_updates": "تحديثات التطبيق",
"about_nuvio": "حول Nuvio" "about_nuvio": "حول Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "الخصوصية والبيانات", "title": "الخصوصية والبيانات",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Това ще нулира всички настройки и ще изчисти всички кеширани данни. Сигурни ли сте?", "clear_data_desc": "Това ще нулира всички настройки и ще изчисти всички кеширани данни. Сигурни ли сте?",
"app_updates": "Обновявания на приложението", "app_updates": "Обновявания на приложението",
"about_nuvio": "Относно Nuvio" "about_nuvio": "Относно Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Поверителност и данни", "title": "Поверителност и данни",

View file

@ -765,7 +765,65 @@
}, },
"clear_data_desc": "Això restablirà tota la configuració i esborrarà totes les dades en memòria cau. Estàs segur?", "clear_data_desc": "Això restablirà tota la configuració i esborrarà totes les dades en memòria cau. Estàs segur?",
"app_updates": "Actualitzacions de l'aplicació", "app_updates": "Actualitzacions de l'aplicació",
"about_nuvio": "Quant a Nuvio" "about_nuvio": "Quant a Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privadesa i dades", "title": "Privadesa i dades",

View file

@ -754,7 +754,65 @@
}, },
"clear_data_desc": "Tímto dojde k resetování všech nastavení a vymazání všech dat v mezipaměti. Jste si jisti?", "clear_data_desc": "Tímto dojde k resetování všech nastavení a vymazání všech dat v mezipaměti. Jste si jisti?",
"app_updates": "Aktualizace aplikace", "app_updates": "Aktualizace aplikace",
"about_nuvio": "O aplikaci Nuvio" "about_nuvio": "O aplikaci Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Soukromí a data", "title": "Soukromí a data",

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "Dies setzt alle Einstellungen zurück und löscht alle zwischengespeicherten Daten. Sind Sie sicher?", "clear_data_desc": "Dies setzt alle Einstellungen zurück und löscht alle zwischengespeicherten Daten. Sind Sie sicher?",
"app_updates": "App-Updates", "app_updates": "App-Updates",
"about_nuvio": "Über Nuvio" "about_nuvio": "Über Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Datenschutz & Daten", "title": "Datenschutz & Daten",

View file

@ -765,65 +765,65 @@
}, },
"clear_data_desc": "This will reset all settings and clear all cached data. Are you sure?", "clear_data_desc": "This will reset all settings and clear all cached data. Are you sure?",
"app_updates": "App Updates", "app_updates": "App Updates",
"about_nuvio": "About Nuvio" "about_nuvio": "About Nuvio",
}, "cloud_sync": {
"cloud_sync": { "title": "Nuvio Sync",
"title": "Nuvio Sync", "description": "Sync data across your Nuvio devices",
"description": "Sync data across your Nuvio devices", "hero_title": "Cloud Sync",
"hero_title": "Cloud Sync", "hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.", "auth": {
"auth": { "account": "Account",
"account": "Account", "not_configured": "Supabase not configured",
"not_configured": "Supabase not configured", "not_authenticated": "Not authenticated",
"not_authenticated": "Not authenticated", "email_session": "Email session",
"email_session": "Email session", "signed_in_as": "Signed in as {{email}}",
"signed_in_as": "Signed in as {{email}}", "not_signed_in": "Not signed in",
"not_signed_in": "Not signed in", "effective_owner": "Effective owner: {{id}}"
"effective_owner": "Effective owner: {{id}}" },
}, "stats": {
"stats": { "title": "Database Statistics",
"title": "Database Statistics", "plugins": "Plugins",
"plugins": "Plugins", "addons": "Addons",
"addons": "Addons", "watch_progress": "Watch Progress",
"watch_progress": "Watch Progress", "library_items": "Library Items",
"library_items": "Library Items", "watched_items": "Watched Items",
"watched_items": "Watched Items", "signin_required": "Sign in to load remote data counts."
"signin_required": "Sign in to load remote data counts." },
}, "actions": {
"actions": { "title": "Actions",
"title": "Actions", "description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.", "pull_btn": "Pull from Cloud",
"pull_btn": "Pull from Cloud", "push_btn": "Push from Device",
"push_btn": "Push from Device", "manage_account": "Manage Account",
"manage_account": "Manage Account", "sign_out": "Sign Out",
"sign_out": "Sign Out", "sign_in_up": "Sign In / Up"
"sign_in_up": "Sign In / Up" },
}, "alerts": {
"alerts": { "pull_success_title": "Cloud Data Pulled",
"pull_success_title": "Cloud Data Pulled", "pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_success_msg": "The latest cloud data has been downloaded to this device.", "pull_failed_title": "Pull Failed",
"pull_failed_title": "Pull Failed", "pull_failed_msg": "Failed to download data from the cloud",
"pull_failed_msg": "Failed to download data from the cloud", "push_success_title": "Push Completed",
"push_success_title": "Push Completed", "push_success_msg": "Device data has been uploaded to the cloud.",
"push_success_msg": "Device data has been uploaded to the cloud.", "push_failed_title": "Push Failed",
"push_failed_title": "Push Failed", "push_failed_msg": "Failed to upload local data",
"push_failed_msg": "Failed to upload local data", "sign_out_failed": "Sign Out Failed",
"sign_out_failed": "Sign Out Failed", "sign_out_failed_title": "Logout Error"
"sign_out_failed_title": "Logout Error" },
}, "external_sync": {
"external_sync": { "title": "External Sync Priority",
"title": "External Sync Priority", "active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.", "inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database." },
}, "pre_auth": {
"pre_auth": { "title": "Before Syncing",
"title": "Before Syncing", "description": "Sign in to start cloud sync and keep your data consistent across devices.",
"description": "Sign in to start cloud sync and keep your data consistent across devices.", "point_1": "• Addons and plugins settings",
"point_1": "• Addons and plugins settings", "point_2": "• Watch progress and library",
"point_2": "• Watch progress and library", "env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync." },
}, "connection": "Connection"
"connection": "Connection" }
}, },
"privacy": { "privacy": {
"title": "Privacy & Data", "title": "Privacy & Data",

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "Esto restablecerá todos los ajustes y borrará todos los datos almacenados en caché. ¿Estás seguro?", "clear_data_desc": "Esto restablecerá todos los ajustes y borrará todos los datos almacenados en caché. ¿Estás seguro?",
"app_updates": "Actualizaciones de la app", "app_updates": "Actualizaciones de la app",
"about_nuvio": "Acerca de Nuvio" "about_nuvio": "Acerca de Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privacidad y Datos", "title": "Privacidad y Datos",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "I-re-reset nito ang lahat ng settings at buburahin ang lahat ng cached data. Sigurado ka ba?", "clear_data_desc": "I-re-reset nito ang lahat ng settings at buburahin ang lahat ng cached data. Sigurado ka ba?",
"app_updates": "Mga Update ng App", "app_updates": "Mga Update ng App",
"about_nuvio": "Tungkol sa Nuvio" "about_nuvio": "Tungkol sa Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privacy at Data", "title": "Privacy at Data",

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "Cela réinitialisera tous les paramètres et effacera toutes les données en cache. Êtes-vous sûr ?", "clear_data_desc": "Cela réinitialisera tous les paramètres et effacera toutes les données en cache. Êtes-vous sûr ?",
"app_updates": "Mises à jour de l'application", "app_updates": "Mises à jour de l'application",
"about_nuvio": "À propos de Nuvio" "about_nuvio": "À propos de Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Confidentialité et Données", "title": "Confidentialité et Données",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "פעולה זו תאפס את כל ההגדרות ותנקה את כל המידע השמור. האם אתם בטוחים?", "clear_data_desc": "פעולה זו תאפס את כל ההגדרות ותנקה את כל המידע השמור. האם אתם בטוחים?",
"app_updates": "עדכוני אפליקציה", "app_updates": "עדכוני אפליקציה",
"about_nuvio": "אודות Nuvio" "about_nuvio": "אודות Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "פרטיות ומידע", "title": "פרטיות ומידע",

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "यह सभी सेटिंग्स को रीसेट कर देगा और सभी कैश किए गए डेटा को साफ़ कर देगा। क्या आप सुनिश्चित हैं?", "clear_data_desc": "यह सभी सेटिंग्स को रीसेट कर देगा और सभी कैश किए गए डेटा को साफ़ कर देगा। क्या आप सुनिश्चित हैं?",
"app_updates": "ऐप अपडेट", "app_updates": "ऐप अपडेट",
"about_nuvio": "Nuvio के बारे में" "about_nuvio": "Nuvio के बारे में",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "गोपनीयता और डेटा", "title": "गोपनीयता और डेटा",

View file

@ -747,7 +747,65 @@
}, },
"clear_data_desc": "Ovo će resetirati sve postavke i obrisati sve privremene podatke. Jeste li sigurni?", "clear_data_desc": "Ovo će resetirati sve postavke i obrisati sve privremene podatke. Jeste li sigurni?",
"app_updates": "Ažuriranja aplikacije", "app_updates": "Ažuriranja aplikacije",
"about_nuvio": "O Nuviju" "about_nuvio": "O Nuviju",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privatnost i Podaci", "title": "Privatnost i Podaci",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Dit zal alle instellingen resetten en alle gecachte gegevens wissen. Weet je het zeker?", "clear_data_desc": "Dit zal alle instellingen resetten en alle gecachte gegevens wissen. Weet je het zeker?",
"app_updates": "App Updates", "app_updates": "App Updates",
"about_nuvio": "Over Nuvio" "about_nuvio": "Over Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privacy & Gegevens", "title": "Privacy & Gegevens",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "To zresetuje wszystkie ustawienia i wyczyści pamięć podręczną. Czy na pewno?", "clear_data_desc": "To zresetuje wszystkie ustawienia i wyczyści pamięć podręczną. Czy na pewno?",
"app_updates": "Aktualizacje aplikacji", "app_updates": "Aktualizacje aplikacji",
"about_nuvio": "O Nuvio" "about_nuvio": "O Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Prywatność i Dane", "title": "Prywatność i Dane",

View file

@ -773,65 +773,65 @@
}, },
"clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Você tem certeza?", "clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Você tem certeza?",
"app_updates": "Atualizações do App", "app_updates": "Atualizações do App",
"about_nuvio": "Sobre o Nuvio" "about_nuvio": "Sobre o Nuvio",
}, "cloud_sync": {
"cloud_sync": { "title": "Nuvio Sync",
"title": "Nuvio Sync", "description": "Sincronize dados entre seus dispositivos Nuvio",
"description": "Sincronize dados entre seus dispositivos Nuvio", "hero_title": "Sincronização na Nuvem",
"hero_title": "Sincronização na Nuvem", "hero_subtitle": "Mantenha seus addons, progresso e biblioteca alinhados em todos os dispositivos.",
"hero_subtitle": "Mantenha seus addons, progresso e biblioteca alinhados em todos os dispositivos.", "auth": {
"auth": { "account": "Conta",
"account": "Conta", "not_configured": "Supabase não configurado",
"not_configured": "Supabase não configurado", "not_authenticated": "Não autenticado",
"not_authenticated": "Não autenticado", "email_session": "Sessão de email",
"email_session": "Sessão de email", "signed_in_as": "Logado como {{email}}",
"signed_in_as": "Logado como {{email}}", "not_signed_in": "Não logado",
"not_signed_in": "Não logado", "effective_owner": "Proprietário efetivo: {{id}}"
"effective_owner": "Proprietário efetivo: {{id}}" },
}, "stats": {
"stats": { "title": "Estatísticas do Banco de Dados",
"title": "Estatísticas do Banco de Dados", "plugins": "Plugins",
"plugins": "Plugins", "addons": "Addons",
"addons": "Addons", "watch_progress": "Progresso",
"watch_progress": "Progresso", "library_items": "Itens na Biblioteca",
"library_items": "Itens na Biblioteca", "watched_items": "Itens Assistidos",
"watched_items": "Itens Assistidos", "signin_required": "Faça login para carregar contagens de dados remotos."
"signin_required": "Faça login para carregar contagens de dados remotos." },
}, "actions": {
"actions": { "title": "Ações",
"title": "Ações", "description": "Baixe para atualizar este dispositivo a partir da nuvem, ou envie deste dispositivo como a fonte mais recente.",
"description": "Baixe para atualizar este dispositivo a partir da nuvem, ou envie deste dispositivo como a fonte mais recente.", "pull_btn": "Baixar da Nuvem",
"pull_btn": "Baixar da Nuvem", "push_btn": "Enviar deste Dispositivo",
"push_btn": "Enviar deste Dispositivo", "manage_account": "Gerenciar Conta",
"manage_account": "Gerenciar Conta", "sign_out": "Sair",
"sign_out": "Sair", "sign_in_up": "Entrar / Cadastrar"
"sign_in_up": "Entrar / Cadastrar" },
}, "alerts": {
"alerts": { "pull_success_title": "Dados da Nuvem Baixados",
"pull_success_title": "Dados da Nuvem Baixados", "pull_success_msg": "Os dados mais recentes da nuvem foram baixados para este dispositivo.",
"pull_success_msg": "Os dados mais recentes da nuvem foram baixados para este dispositivo.", "pull_failed_title": "Falha ao Baixar",
"pull_failed_title": "Falha ao Baixar", "pull_failed_msg": "Falha ao baixar dados da nuvem",
"pull_failed_msg": "Falha ao baixar dados da nuvem", "push_success_title": "Envio Concluído",
"push_success_title": "Envio Concluído", "push_success_msg": "Os dados deste dispositivo foram enviados para a nuvem.",
"push_success_msg": "Os dados deste dispositivo foram enviados para a nuvem.", "push_failed_title": "Falha ao Enviar",
"push_failed_title": "Falha ao Enviar", "push_failed_msg": "Falha ao enviar dados locais",
"push_failed_msg": "Falha ao enviar dados locais", "sign_out_failed": "Falha ao Sair",
"sign_out_failed": "Falha ao Sair", "sign_out_failed_title": "Erro de Saida"
"sign_out_failed_title": "Erro de Saida" },
}, "external_sync": {
"external_sync": { "title": "Prioridade de Sincronização Externa",
"title": "Prioridade de Sincronização Externa", "active_msg": "{{services}} está ativo. O progresso e atualizações da biblioteca são gerenciados por esses serviços em vez do banco de dados Nuvio.",
"active_msg": "{{services}} está ativo. O progresso e atualizações da biblioteca são gerenciados por esses serviços em vez do banco de dados Nuvio.", "inactive_msg": "Se a sincronização do Trakt ou Simkl estiver ativada, o progresso e a biblioteca usarão esses serviços em vez do banco de dados Nuvio."
"inactive_msg": "Se a sincronização do Trakt ou Simkl estiver ativada, o progresso e a biblioteca usarão esses serviços em vez do banco de dados Nuvio." },
}, "pre_auth": {
"pre_auth": { "title": "Antes de Sincronizar",
"title": "Antes de Sincronizar", "description": "Faça login para iniciar a sincronização na nuvem e manter seus dados consistentes entre dispositivos.",
"description": "Faça login para iniciar a sincronização na nuvem e manter seus dados consistentes entre dispositivos.", "point_1": "• Configurações de addons e plugins",
"point_1": "• Configurações de addons e plugins", "point_2": "• Progresso de exibição e biblioteca",
"point_2": "• Progresso de exibição e biblioteca", "env_warning": "Defina EXPO_PUBLIC_SUPABASE_URL e EXPO_PUBLIC_SUPABASE_ANON_KEY para habilitar a sincronização."
"env_warning": "Defina EXPO_PUBLIC_SUPABASE_URL e EXPO_PUBLIC_SUPABASE_ANON_KEY para habilitar a sincronização." },
}, "connection": "Conexão"
"connection": "Conexão" }
}, },
"privacy": { "privacy": {
"title": "Privacidade e Dados", "title": "Privacidade e Dados",

View file

@ -771,7 +771,65 @@
}, },
"clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Tens a certeza?", "clear_data_desc": "Isso redefinirá todas as configurações e limpará todos os dados em cache. Tens a certeza?",
"app_updates": "Atualizações da App", "app_updates": "Atualizações da App",
"about_nuvio": "Sobre o Nuvio" "about_nuvio": "Sobre o Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privacidade e Dados", "title": "Privacidade e Dados",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Această acțiune va reseta toate setările și va șterge toate datele cache. Ești sigur?", "clear_data_desc": "Această acțiune va reseta toate setările și va șterge toate datele cache. Ești sigur?",
"app_updates": "Actualizări aplicație", "app_updates": "Actualizări aplicație",
"about_nuvio": "Despre Nuvio" "about_nuvio": "Despre Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Confidențialitate și Date", "title": "Confidențialitate și Date",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Это сбросит все настройки и очистит кэш. Вы уверены?", "clear_data_desc": "Это сбросит все настройки и очистит кэш. Вы уверены?",
"app_updates": "Обновления приложения", "app_updates": "Обновления приложения",
"about_nuvio": "О Nuvio" "about_nuvio": "О Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Конфиденциальность", "title": "Конфиденциальность",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "To bo ponastavilo vse nastavitve in počistilo predpomnilnik. Ste prepričani?", "clear_data_desc": "To bo ponastavilo vse nastavitve in počistilo predpomnilnik. Ste prepričani?",
"app_updates": "Posodobitve aplikacije", "app_updates": "Posodobitve aplikacije",
"about_nuvio": "O Nuvio" "about_nuvio": "O Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Zasebnost in podatki", "title": "Zasebnost in podatki",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Kjo do të rikthejë të gjitha cilësimet dhe do të fshijë të gjithë cache-in. Jeni të sigurt?", "clear_data_desc": "Kjo do të rikthejë të gjitha cilësimet dhe do të fshijë të gjithë cache-in. Jeni të sigurt?",
"app_updates": "Përditësimet e Aplikacionit", "app_updates": "Përditësimet e Aplikacionit",
"about_nuvio": "Rreth Nuvio" "about_nuvio": "Rreth Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Privatësia & Të dhënat", "title": "Privatësia & Të dhënat",

View file

@ -1,426 +1,426 @@
{ {
"common": { "common": {
"loading": "Учитавање...", "loading": "Учитавање...",
"cancel": "Одустани", "cancel": "Одустани",
"save": "Сачувај", "save": "Сачувај",
"delete": "Обриши", "delete": "Обриши",
"edit": "Уреди", "edit": "Уреди",
"search": "Претражи", "search": "Претражи",
"error": "Грешка", "error": "Грешка",
"success": "Успех", "success": "Успех",
"ok": "У реду", "ok": "У реду",
"unknown": "Непознато", "unknown": "Непознато",
"retry": "Покушај поново", "retry": "Покушај поново",
"try_again": "Покушај поново", "try_again": "Покушај поново",
"go_back": "Иди назад", "go_back": "Иди назад",
"settings": "Подешавања", "settings": "Подешавања",
"close": "Затвори", "close": "Затвори",
"enable": "Омогући", "enable": "Омогући",
"disable": "Искључи", "disable": "Искључи",
"show_more": "Прикажи више", "show_more": "Прикажи више",
"show_less": "Прикажи мање", "show_less": "Прикажи мање",
"load_more": "Учитај више", "load_more": "Учитај више",
"unknown_date": "Непознат датум", "unknown_date": "Непознат датум",
"anonymous_user": "Анонимни корисник", "anonymous_user": "Анонимни корисник",
"time": { "time": {
"now": "Управо сада", "now": "Управо сада",
"minutes_ago": "пре {{count}} мин", "minutes_ago": "пре {{count}} мин",
"hours_ago": "пре {{count}} ч", "hours_ago": "пре {{count}} ч",
"days_ago": "пре {{count}} д" "days_ago": "пре {{count}} д"
},
"days_short": {
"sun": "Нед",
"mon": "Пон",
"tue": "Уто",
"wed": "Сре",
"thu": "Чет",
"fri": "Пет",
"sat": "Суб"
},
"email": "Е-пошта",
"status": "Статус"
}, },
"days_short": { "home": {
"sun": "Нед", "categories": {
"mon": "Пон", "movies": "Филмови",
"tue": "Уто", "series": "Серије",
"wed": "Сре", "channels": "Канали"
"thu": "Чет", },
"fri": "Пет", "movies": "Филмови",
"sat": "Суб" "tv_shows": "Cерије",
"load_more_catalogs": "Учитај више каталога",
"no_content": "Садржај није доступан",
"add_catalogs": "Додај каталоге",
"sign_in_available": "Пријава доступна",
"sign_in_desc": "Можете се пријавити било када у Подешавања → Налог",
"view_all": "Види све",
"this_week": "Ове недеље",
"upcoming": "Надолазеће",
"recently_released": "Недавно објављено",
"no_scheduled_episodes": "Серије без заказаних епизода",
"check_back_later": "Проверите касније",
"continue_watching": "Настави са гледањем",
"up_next": "Следеће на реду",
"up_next_caps": "СЛЕДЕЋЕ",
"released": "Објављено",
"new": "Ново",
"tba": "Биће објављено",
"new_episodes": "{{count}} нове епизоде",
"season_short": "С{{season}}",
"episode_short": "Е{{episode}}",
"season": "Сезона {{season}}",
"episode": "Епизода {{episode}}",
"movie": "Филм",
"series": "Серија",
"tv_show": "Cерија",
"percent_watched": "{{percent}}% одгледано",
"view_details": "Види детаље",
"remove": "Уклони",
"play": "Пусти",
"play_now": "Пусти одмах",
"resume": "Настави",
"info": "Инфо",
"more_info": "Више информација",
"my_list": "Моја листа",
"save": "Сачувај",
"saved": "Сачувано",
"retry": "Покушај поново",
"install_addons": "Инсталирај додатке",
"settings": "Подешавања",
"no_featured_content": "Нема истакнутог садржаја",
"couldnt_load_featured": "Учитавање истакнутог садржаја није успело",
"no_featured_desc": "Инсталирајте додатке са каталозима или промените извор садржаја у подешавањима.",
"load_error_desc": "Дошло је до проблема при преузимању садржаја. Проверите везу и покушајте поново.",
"no_featured_available": "Нема доступног истакнутог садржаја",
"no_description": "Опис није доступан"
}, },
"email": "Е-пошта", "navigation": {
"status": "Статус" "home": "Почетна",
}, "library": "Библиотека",
"home": { "search": "Претрага",
"categories": { "downloads": "Преузимања",
"movies": "Филмови", "settings": "Подешавања"
"series": "Серије",
"channels": "Канали"
}, },
"movies": "Филмови", "search": {
"tv_shows": "Cерије", "title": "Претрага",
"load_more_catalogs": "Учитај више каталога", "recent_searches": "Недавне претраге",
"no_content": "Садржај није доступан", "discover": "Откриј",
"add_catalogs": "Додај каталоге", "movies": "Филмови",
"sign_in_available": "Пријава доступна", "tv_shows": "Cерије",
"sign_in_desc": "Можете се пријавити било када у Подешавања → Налог", "select_catalog": "Изабери каталог",
"view_all": "Види све", "all_genres": "Сви жанрови",
"this_week": "Ове недеље", "discovering": "Откривање садржаја...",
"upcoming": "Надолазеће", "show_more": "Прикажи више ({{count}})",
"recently_released": "Недавно објављено", "no_content_found": "Садржај није пронађен",
"no_scheduled_episodes": "Серије без заказаних епизода", "try_different": "Покушајте други жанр или каталог",
"check_back_later": "Проверите касније", "select_catalog_desc": "Изаберите каталог за истраживање",
"continue_watching": "Настави са гледањем", "tap_catalog_desc": "Додирните дугме каталога изнад за почетак",
"up_next": "Следеће на реду", "placeholder": "Претражи филмове, серије...",
"up_next_caps": "СЛЕДЕЋЕ", "keep_typing": "Наставите са куцањем...",
"released": "Објављено", "type_characters": "Упишите најмање 2 знака за претрагу",
"new": "Ново", "no_results": "Нема резултата",
"tba": "Биће објављено", "try_keywords": "Покушајте са другим кључним речима",
"new_episodes": "{{count}} нове епизоде", "select_type": "Изабери тип",
"season_short": "С{{season}}", "browse_movies": "Прегледај каталоге филмова",
"episode_short": "Е{{episode}}", "browse_tv": "Прегледај каталоге cерија",
"season": "Сезона {{season}}", "select_genre": "Изабери жанр",
"episode": "Епизода {{episode}}", "show_all_content": "Прикажи сав садржај",
"movie": "Филм", "genres_count": "{{count}} жанрова"
"series": "Серија", },
"tv_show": "Cерија", "library": {
"percent_watched": "{{percent}}% одгледано", "title": "Библиотека",
"view_details": "Види детаље", "watched": "Одгледано",
"remove": "Уклони", "continue": "Настави",
"play": "Пусти", "watchlist": "Листа за гледање",
"play_now": "Пусти одмах", "collection": "Колекција",
"resume": "Настави", "rated": "Оцењено",
"info": "Инфо", "items": "ставки",
"more_info": "Више информација", "trakt_collections": "Trakt колекције",
"my_list": "Моја листа", "trakt_collection": "Trakt колекција",
"save": "Сачувај", "no_trakt": "Нема Trakt колекција",
"saved": "Сачувано", "no_trakt_desc": "Ваше Trakt колекције ће се појавити овде након коришћења Trakt-а",
"retry": "Покушај поново", "load_collections": "Учитај колекције",
"install_addons": "Инсталирај додатке", "empty_folder": "Нема садржаја у {{folder}}",
"settings": "Подешавања", "empty_folder_desc": "Ова колекција је празна",
"no_featured_content": "Нема истакнутог садржаја", "refresh": "Освежи",
"couldnt_load_featured": "Учитавање истакнутог садржаја није успело", "no_movies": "Још нема филмова",
"no_featured_desc": "Инсталирајте додатке са каталозима или промените извор садржаја у подешавањима.", "no_series": "Још нема cерија",
"load_error_desc": "Дошло је до проблема при преузимању садржаја. Проверите везу и покушајте поново.", "no_content": "Још нема садржаја",
"no_featured_available": "Нема доступног истакнутог садржаја", "add_content_desc": "Додајте садржај у своју библиотеку да бисте га видели овде",
"no_description": "Опис није доступан" "find_something": "Пронађи нешто за гледање",
}, "removed_from_library": "Уклоњено из библиотеке",
"navigation": { "item_removed": "Ставка је уклоњена из ваше библиотеке",
"home": "Почетна", "failed_update_library": "Ажурирање библиотеке није успело",
"library": "Библиотека", "unable_remove": "Неуспело уклањање ставке из библиотеке",
"search": "Претрага", "marked_watched": "Означено као одгледано",
"downloads": "Преузимања", "marked_unwatched": "Означено као неодгледано",
"settings": "Подешавања" "item_marked_watched": "Ставка је означена као одгледана",
}, "item_marked_unwatched": "Ставка је означена као неодгледана",
"search": { "failed_update_watched": "Ажурирање статуса гледања није успело",
"title": "Претрага", "unable_update_watched": "Неуспело ажурирање статуса гледања",
"recent_searches": "Недавне претраге", "added_to_library": "Додато у библиотеку",
"discover": "Откриј", "item_added": "Додато у вашу локалну библиотеку",
"movies": "Филмови", "add_to_library": "Додај у библиотеку",
"tv_shows": "Cерије", "remove_from_library": "Уклони из библиотеке",
"select_catalog": "Изабери каталог", "mark_watched": "Означи као одгледано",
"all_genres": "Сви жанрови", "mark_unwatched": "Означи као неодгледано",
"discovering": "Откривање садржаја...", "share": "Подели",
"show_more": "Прикажи више ({{count}})", "add_to_watchlist": "Додај на Trakt листу гледања",
"no_content_found": "Садржај није пронађен", "remove_from_watchlist": "Уклони са Trakt листе гледања",
"try_different": "Покушајте други жанр или каталог", "added_to_watchlist": "Додато на листу гледања",
"select_catalog_desc": "Изаберите каталог за истраживање", "added_to_watchlist_desc": "Додато на вашу Trakt листу гледања",
"tap_catalog_desc": "Додирните дугме каталога изнад за почетак", "removed_from_watchlist": "Уклоњено са листе гледања",
"placeholder": "Претражи филмове, серије...", "removed_from_watchlist_desc": "Уклоњено са ваше Trakt листе гледања",
"keep_typing": "Наставите са куцањем...", "add_to_collection": "Додај у Trakt колекцију",
"type_characters": "Упишите најмање 2 знака за претрагу", "remove_from_collection": "Уклони из Trakt колекције",
"no_results": "Нема резултата", "added_to_collection": "Додато у колекцију",
"try_keywords": "Покушајте са другим кључним речима", "added_to_collection_desc": "Додато у вашу Trakt колекцију",
"select_type": "Изабери тип", "removed_from_collection": "Уклоњено из колекције",
"browse_movies": "Прегледај каталоге филмова", "removed_from_collection_desc": "Уклоњено из ваше Trakt колекције"
"browse_tv": "Прегледај каталоге cерија", },
"select_genre": "Изабери жанр", "metadata": {
"show_all_content": "Прикажи сав садржај", "unable_to_load": "Учитавање садржаја није успело",
"genres_count": "{{count}} жанрова" "error_code": "Код грешке: {{code}}",
}, "content_not_found": "Садржај није пронађен",
"library": { "content_not_found_desc": "Овај садржај не постоји или је можда уклоњен.",
"title": "Библиотека", "server_error": "Грешка сервера",
"watched": "Одгледано", "server_error_desc": "Сервер је привремено недоступан. Покушајте касније.",
"continue": "Настави", "bad_gateway": "Лош gateway",
"watchlist": "Листа за гледање", "bad_gateway_desc": "Сервер има потешкоћа. Покушајте касније.",
"collection": "Колекција", "service_unavailable": "Услуга недоступна",
"rated": "Оцењено", "service_unavailable_desc": "Услуга је тренутно недоступна због одржавања.",
"items": "ставки", "too_many_requests": "Превише захтева",
"trakt_collections": "Trakt колекције", "too_many_requests_desc": "Шаљете превише захтева. Молимо сачекајте тренутак.",
"trakt_collection": "Trakt колекција", "request_timeout": "Истек захтева",
"no_trakt": "Нема Trakt колекција", "request_timeout_desc": "Захтев је трајао предуго. Покушајте поново.",
"no_trakt_desc": "Ваше Trakt колекције ће се појавити овде након коришћења Trakt-а", "network_error": "Мрежна грешка",
"load_collections": "Учитај колекције", "network_error_desc": "Проверите интернет везу и покушајте поново.",
"empty_folder": "Нема садржаја у {{folder}}", "auth_error": "Грешка у аутентификацији",
"empty_folder_desc": "Ова колекција је празна", "auth_error_desc": "Проверите подешавања налога и покушајте поново.",
"refresh": "Освежи", "access_denied": "Приступ одбијен",
"no_movies": "Још нема филмова", "access_denied_desc": "Немате дозволу за приступ овом садржају.",
"no_series": "Још нема cерија", "connection_error": "Грешка у вези",
"no_content": "Још нема садржаја", "streams_unavailable": "Стримови недоступни",
"add_content_desc": "Додајте садржај у своју библиотеку да бисте га видели овде", "streams_unavailable_desc": "Извори за стримовање су тренутно недоступни.",
"find_something": "Пронађи нешто за гледање", "unknown_error": "Непозната грешка",
"removed_from_library": "Уклоњено из библиотеке", "something_went_wrong": "Нешто је пошло по злу. Покушајте поново.",
"item_removed": "Ставка је уклоњена из ваше библиотеке", "cast": "Глумачка постава",
"failed_update_library": "Ажурирање библиотеке није успело", "more_like_this": "Слично овоме",
"unable_remove": "Неуспело уклањање ставке из библиотеке", "collection": "Колекција",
"marked_watched": "Означено као одгледано", "episodes": "Епизоде",
"marked_unwatched": "Означено као неодгледано", "seasons": "Сезоне",
"item_marked_watched": "Ставка је означена као одгледана", "posters": "Постери",
"item_marked_unwatched": "Ставка је означена као неодгледана", "banners": "Банери",
"failed_update_watched": "Ажурирање статуса гледања није успело", "specials": "Специјали",
"unable_update_watched": "Неуспело ажурирање статуса гледања", "season_number": "Сезона {{number}}",
"added_to_library": "Додато у библиотеку", "episode_count": "{{count}} епизода",
"item_added": "Додато у вашу локалну библиотеку", "episode_count_plural": "{{count}} епизода",
"add_to_library": "Додај у библиотеку", "no_episodes": "Нема доступних епизода",
"remove_from_library": "Уклони из библиотеке", "no_episodes_for_season": "Нема доступних епизода за сезону {{season}}",
"mark_watched": "Означи као одгледано", "episodes_not_released": "Епизоде можда још нису објављене",
"mark_unwatched": "Означи као неодгледано", "no_description": "Опис није доступан",
"share": "Подели", "episode_label": "ЕПИЗОДА {{number}}",
"add_to_watchlist": "Додај на Trakt листу гледања", "watch_again": "Гледај поново",
"remove_from_watchlist": "Уклони са Trakt листе гледања", "completed": "Завршено",
"added_to_watchlist": "Додато на листу гледања", "play_episode": "Пусти С{{season}}Е{{episode}}",
"added_to_watchlist_desc": "Додато на вашу Trakt листу гледања", "play": "Пусти",
"removed_from_watchlist": "Уклоњено са листе гледања", "watched": "Одгледано",
"removed_from_watchlist_desc": "Уклоњено са ваше Trakt листе гледања", "watched_on_trakt": "Одгледано на Trakt-у",
"add_to_collection": "Додај у Trakt колекцију", "synced_with_trakt": "Синхронизовано са Trakt-ом",
"remove_from_collection": "Уклони из Trakt колекције", "saved": "Сачувано",
"added_to_collection": "Додато у колекцију", "director": "Режисер",
"added_to_collection_desc": "Додато у вашу Trakt колекцију", "directors": "Режисери",
"removed_from_collection": "Уклоњено из колекције", "creator": "Креатор",
"removed_from_collection_desc": "Уклоњено из ваше Trakt колекције" "creators": "Креатори",
}, "production": "Продукција",
"metadata": { "network": "Мрежа",
"unable_to_load": "Учитавање садржаја није успело", "mark_watched": "Означи као одгледано",
"error_code": "Код грешке: {{code}}", "mark_unwatched": "Означи као неодгледано",
"content_not_found": "Садржај није пронађен", "marking": "Означавање...",
"content_not_found_desc": "Овај садржај не постоји или је можда уклоњен.", "removing": "Уклањање...",
"server_error": "Грешка сервера", "unmark_season": "Одозначи сезону {{season}}",
"server_error_desc": "Сервер је привремено недоступан. Покушајте касније.", "mark_season": "Означи сезону {{season}}",
"bad_gateway": "Лош gateway", "resume": "Настави",
"bad_gateway_desc": "Сервер има потешкоћа. Покушајте касније.", "spoiler_warning": "Упозорење о спојлерима",
"service_unavailable": "Услуга недоступна", "spoiler_warning_desc": "Овај коментар садржи спојлере. Да ли сте сигурни да желите да га откријете?",
"service_unavailable_desc": "Услуга је тренутно недоступна због одржавања.", "cancel": "Одустани",
"too_many_requests": "Превише захтева", "reveal_spoilers": "Откриј спојлере",
"too_many_requests_desc": "Шаљете превише захтева. Молимо сачекајте тренутак.", "movie_details": "Детаљи о филму",
"request_timeout": "Истек захтева", "show_details": "Детаљи о серији",
"request_timeout_desc": "Захтев је трајао предуго. Покушајте поново.", "tagline": "Слоган",
"network_error": "Мрежна грешка", "status": "Статус",
"network_error_desc": "Проверите интернет везу и покушајте поново.", "release_date": "Датум објаве",
"auth_error": "Грешка у аутентификацији", "runtime": "Трајање",
"auth_error_desc": "Проверите подешавања налога и покушајте поново.", "budget": "Буџет",
"access_denied": "Приступ одбијен", "revenue": "Приход",
"access_denied_desc": "Немате дозволу за приступ овом садржају.", "origin_country": "Земља порекла",
"connection_error": "Грешка у вези", "original_language": "Оригинални језик",
"streams_unavailable": "Стримови недоступни", "first_air_date": "Прво емитовање",
"streams_unavailable_desc": "Извори за стримовање су тренутно недоступни.", "last_air_date": "Последње емитовање",
"unknown_error": "Непозната грешка", "total_episodes": "Укупно епизода",
"something_went_wrong": "Нешто је пошло по злу. Покушајте поново.", "episode_runtime": "Трајање епизоде",
"cast": "Глумачка постава", "created_by": "Креирао",
"more_like_this": "Слично овоме", "backdrop_gallery": "Галерија позадина",
"collection": "Колекција", "loading_episodes": "Учитавање епизода...",
"episodes": "Епизоде", "no_episodes_available": "Нема доступних епизода",
"seasons": "Сезоне", "play_next": "Пусти С{{season}}Е{{episode}}",
"posters": "Постери", "play_next_episode": "Пусти следећу епизоду",
"banners": "Банери", "save": "Сачувај",
"specials": "Специјали", "percent_watched_trakt": "{{percent}}% одгледано ({{traktPercent}}% на Trakt-у)",
"season_number": "Сезона {{number}}", "synced_with_trakt_progress": "Синхронизовано са Trakt-ом",
"episode_count": "{{count}} епизода", "using_trakt_progress": "Користи се Trakt напредак",
"episode_count_plural": "{{count}} епизода", "added_to_collection_hero": "Додато у колекцију",
"no_episodes": "Нема доступних епизода", "added_to_collection_desc_hero": "Додато у вашу Trakt колекцију",
"no_episodes_for_season": "Нема доступних епизода за сезону {{season}}", "removed_from_collection_hero": "Уклоњено из колекције",
"episodes_not_released": "Епизоде можда још нису објављене", "removed_from_collection_desc_hero": "Уклоњено из ваше Trakt колекције",
"no_description": "Опис није доступан", "mark_as_watched": "Означи као одгледано",
"episode_label": "ЕПИЗОДА {{number}}", "mark_as_unwatched": "Означи као неодгледано"
"watch_again": "Гледај поново", },
"completed": "Завршено", "cast": {
"play_episode": "Пусти С{{season}}Е{{episode}}", "biography": "Биографија",
"play": "Пусти", "known_for": "Познат по",
"watched": "Одгледано", "personal_info": "Лични подаци",
"watched_on_trakt": "Одгледано на Trakt-у", "born_in": "Рођен у {{place}}",
"synced_with_trakt": "Синхронизовано са Trakt-ом", "filmography": "Филмографија",
"saved": "Сачувано", "also_known_as": "Познат и као",
"director": "Режисер", "no_info_available": "Додатне информације нису доступне",
"directors": "Режисери", "as_character": "као {{character}}",
"creator": "Креатор", "loading_details": "Учитавање детаља...",
"creators": "Креатори", "years_old": "{{age}} година",
"production": "Продукција", "view_filmography": "Види филмографију",
"network": "Мрежа", "filter": "Филтер",
"mark_watched": "Означи као одгледано", "sort_by": "Сортирај по",
"mark_unwatched": "Означи као неодгледано", "sort_popular": "Популарно",
"marking": "Означавање...", "sort_latest": "Најновије",
"removing": "Уклањање...", "sort_upcoming": "Надолазеће",
"unmark_season": "Одозначи сезону {{season}}", "upcoming_badge": "НАДОЛАЗЕЋЕ",
"mark_season": "Означи сезону {{season}}", "coming_soon": "Ускоро стиже",
"resume": "Настави", "filmography_count": "Филмографија • {{count}} наслова",
"spoiler_warning": "Упозорење о спојлерима", "loading_filmography": "Учитавање филмографије...",
"spoiler_warning_desc": "Овај коментар садржи спојлере. Да ли сте сигурни да желите да га откријете?", "load_more_remaining": "Учитај још ({{count}} преостало)",
"cancel": "Одустани", "alert_error_title": "Грешка",
"reveal_spoilers": "Откриј спојлере", "alert_error_message": "Неуспело учитавање \"{{title}}\". Покушајте касније.",
"movie_details": "Детаљи о филму", "alert_ok": "У реду",
"show_details": "Детаљи о серији", "no_upcoming": "Нема надолазећих објава за овог глумца",
"tagline": "Слоган", "no_content": "Нема доступног садржаја за овог глумца",
"status": "Статус", "no_movies": "Нема доступних филмова за овог глумца",
"release_date": "Датум објаве", "no_tv": "Нема доступних серија за овог глумца"
"runtime": "Трајање", },
"budget": "Буџет", "comments": {
"revenue": "Приход", "title": "Trakt коментари",
"origin_country": "Земља порекла", "spoiler_warning": "⚠️ Овај коментар садржи спојлере. Додирните да откријете.",
"original_language": "Оригинални језик", "spoiler": "Спојлер",
"first_air_date": "Прво емитовање", "contains_spoilers": "Садржи спојлере",
"last_air_date": "Последње емитовање", "reveal": "Откриј",
"total_episodes": "Укупно епизода", "vip": "VIP",
"episode_runtime": "Трајање епизоде", "unavailable": "Коментари недоступни",
"created_by": "Креирао", "no_comments": "Још нема коментара на Trakt-у",
"backdrop_gallery": "Галерија позадина", "not_in_database": "Овај садржај можда још није у Trakt бази података",
"loading_episodes": "Учитавање епизода...", "check_trakt": "Провери Trakt"
"no_episodes_available": "Нема доступних епизода", },
"play_next": "Пусти С{{season}}Е{{episode}}", "trailers": {
"play_next_episode": "Пусти следећу епизоду", "title": "Трејлери",
"save": "Сачувај", "official_trailers": "Званични трејлери",
"percent_watched_trakt": "{{percent}}% одгледано ({{traktPercent}}% на Trakt-у)", "official_trailer": "Званични трејлер",
"synced_with_trakt_progress": "Синхронизовано са Trakt-ом", "teasers": "Тизери",
"using_trakt_progress": "Користи се Trakt напредак", "teaser": "Тизер",
"added_to_collection_hero": "Додато у колекцију", "clips_scenes": "Клипови и сцене",
"added_to_collection_desc_hero": "Додато у вашу Trakt колекцију", "clip": "Клип",
"removed_from_collection_hero": "Уклоњено из колекције", "featurettes": "Кратки филмови о снимању",
"removed_from_collection_desc_hero": "Уклоњено из ваше Trakt колекције", "featurette": "Кратки филм о снимању",
"mark_as_watched": "Означи као одгледано", "behind_the_scenes": "Иза кулиса",
"mark_as_unwatched": "Означи као неодгледано" "no_trailers": "Нема доступних трејлера",
}, "unavailable": "Трејлер недоступан",
"cast": { "unavailable_desc": "Овај трејлер није могуће учитати тренутно.",
"biography": "Биографија", "unable_to_play": "Није могуће пустити трејлер. Покушајте поново.",
"known_for": "Познат по", "watch_on_youtube": "Гледај на YouTube-у"
"personal_info": "Лични подаци", },
"born_in": "Рођен у {{place}}", "catalog": {
"filmography": "Филмографија", "no_content_found": "Садржај није пронађен",
"also_known_as": "Познат и као", "no_content_filters": "Нема садржаја за изабране филтере",
"no_info_available": "Додатне информације нису доступне", "loading_content": "Учитавање садржаја...",
"as_character": "као {{character}}", "back": "Назад",
"loading_details": "Учитавање детаља...", "in_theaters": "У биоскопима",
"years_old": "{{age}} година", "all": "Све",
"view_filmography": "Види филмографију", "failed_tmdb": "Неуспело учитавање садржаја са TMDB",
"filter": "Филтер", "movies": "Филмови",
"sort_by": "Сортирај по", "tv_shows": "Cерије",
"sort_popular": "Популарно", "channels": "Канали"
"sort_latest": "Најновије", },
"sort_upcoming": "Надолазеће", "streams": {
"upcoming_badge": "НАДОЛАЗЕЋЕ", "back_to_episodes": "Назад на епизоде",
"coming_soon": "Ускоро стиже", "back_to_info": "Назад на инфо",
"filmography_count": "Филмографија • {{count}} наслова", "fetching_from": "Преузимање са:",
"loading_filmography": "Учитавање филмографије...", "no_sources_available": "Нема доступних извора за стримовање",
"load_more_remaining": "Учитај још ({{count}} преостало)", "add_sources_desc": "Молимо додајте изворе у подешавањима",
"alert_error_title": "Грешка", "add_sources": "Додај изворе",
"alert_error_message": "Неуспело учитавање \"{{title}}\". Покушајте касније.", "finding_streams": "Проналажење доступних стримова...",
"alert_ok": "У реду", "finding_best_stream": "Проналажење најбољег стрима за аутоматско пуштање...",
"no_upcoming": "Нема надолазећих објава за овог глумца", "still_fetching": "Преузимање стримова је још у току...",
"no_content": "Нема доступног садржаја за овог глумца", "no_streams_available": "Нема доступних стримова",
"no_movies": "Нема доступних филмова за овог глумца", "starting_best_stream": "Покретање најбољег стрима...",
"no_tv": "Нема доступних серија за овог глумца" "loading_more_sources": "Учитавање додатних извора..."
}, },
"comments": { "player_ui": {
"title": "Trakt коментари", "via": "преко {{name}}",
"spoiler_warning": "⚠️ Овај коментар садржи спојлере. Додирните да откријете.", "audio_tracks": "Аудио траке",
"spoiler": "Спојлер", "no_audio_tracks": "Нема доступних аудио трака",
"contains_spoilers": "Садржи спојлере", "playback_speed": "Брзина репродукције",
"reveal": "Откриј", "on_hold": "На чекању",
"vip": "VIP", "playback_error": "Грешка при репродукцији",
"unavailable": "Коментари недоступни", "unknown_error": "Дошло је до непознате грешке током репродукције.",
"no_comments": "Још нема коментара на Trakt-у", "copy_error": "Копирај детаље о грешци",
"not_in_database": "Овај садржај можда још није у Trakt бази података", "copied_to_clipboard": "Копирано у привремени меморију",
"check_trakt": "Провери Trakt" "dismiss": "Одбаци",
}, "continue_watching": "Настави са гледањем",
"trailers": { "start_over": "Крени испочетка",
"title": "Трејлери", "resume": "Настави",
"official_trailers": "Званични трејлери", "change_source": "Промени извор",
"official_trailer": "Званични трејлер", "switching_source": "Промена извора...",
"teasers": "Тизери", "no_sources_found": "Извори нису пронађени",
"teaser": "Тизер", "sources": "Извори",
"clips_scenes": "Клипови и сцене", "finding_sources": "Проналажење извора...",
"clip": "Клип", "unknown_source": "Непознат извор",
"featurettes": "Кратки филмови о снимању", "sources_limited": "Извори могу бити ограничени због грешака провајдера.",
"featurette": "Кратки филм о снимању", "episodes": "Епизоде",
"behind_the_scenes": "Иза кулиса", "specials": "Специјали",
"no_trailers": "Нема доступних трејлера", "season": "Сезона {{season}}",
"unavailable": "Трејлер недоступан", "stream": "Стрим {{number}}",
"unavailable_desc": "Овај трејлер није могуће учитати тренутно.", "subtitles": "Титлови",
"unable_to_play": "Није могуће пустити трејлер. Покушајте поново.", "built_in": "Уграђени",
"watch_on_youtube": "Гледај на YouTube-у" "addons": "Додаци",
}, "style": "Стил",
"catalog": { "none": "Ниједан",
"no_content_found": "Садржај није пронађен", "search_online_subtitles": "Претражи титлове на мрежи",
"no_content_filters": "Нема садржаја за изабране филтере", "preview": "Преглед",
"loading_content": "Учитавање садржаја...", "quick_presets": "Брза подешавања",
"back": "Назад", "default": "Подразумевано",
"in_theaters": "У биоскопима", "yellow": "Жуто",
"all": "Све", "high_contrast": "Висок контраст",
"failed_tmdb": "Неуспело учитавање садржаја са TMDB", "large": "Велико",
"movies": "Филмови", "core": "Језгро",
"tv_shows": "Cерије", "font_size": "Величина фонта",
"channels": "Канали" "show_background": "Прикажи позадину",
}, "advanced": "Напредно",
"streams": { "position": "Позиција",
"back_to_episodes": "Назад на епизоде", "text_color": "Боја текста",
"back_to_info": "Назад на инфо", "align": "Поравнање",
"fetching_from": "Преузимање са:", "bottom_offset": "Размак од дна",
"no_sources_available": "Нема доступних извора за стримовање", "background_opacity": "Провидност позадине",
"add_sources_desc": "Молимо додајте изворе у подешавањима", "text_shadow": "Сенка текста",
"add_sources": "Додај изворе", "on": "Укључено",
"finding_streams": "Проналажење доступних стримова...", "off": "Искључено",
"finding_best_stream": "Проналажење најбољег стрима за аутоматско пуштање...", "outline_color": "Боја оквира",
"still_fetching": "Преузимање стримова је још у току...", "outline": "Оквир",
"no_streams_available": "Нема доступних стримова", "outline_width": "Ширина оквира",
"starting_best_stream": "Покретање најбољег стрима...", "letter_spacing": "Размак између слова",
"loading_more_sources": "Учитавање додатних извора..." "line_height": "Висина линије",
}, "timing_offset": "Временски помак (с)",
"player_ui": { "visual_sync": "Визуелна синхронизација",
"via": "преко {{name}}", "timing_hint": "Померите титлове раније (-) или касније (+) за синхронизацију.",
"audio_tracks": "Аудио траке", "reset_defaults": "Врати на подразумевано"
"no_audio_tracks": "Нема доступних аудио трака", },
"playback_speed": "Брзина репродукције", "downloads": {
"on_hold": "На чекању",
"playback_error": "Грешка при репродукцији",
"unknown_error": "Дошло је до непознате грешке током репродукције.",
"copy_error": "Копирај детаље о грешци",
"copied_to_clipboard": "Копирано у привремени меморију",
"dismiss": "Одбаци",
"continue_watching": "Настави са гледањем",
"start_over": "Крени испочетка",
"resume": "Настави",
"change_source": "Промени извор",
"switching_source": "Промена извора...",
"no_sources_found": "Извори нису пронађени",
"sources": "Извори",
"finding_sources": "Проналажење извора...",
"unknown_source": "Непознат извор",
"sources_limited": "Извори могу бити ограничени због грешака провајдера.",
"episodes": "Епизоде",
"specials": "Специјали",
"season": "Сезона {{season}}",
"stream": "Стрим {{number}}",
"subtitles": "Титлови",
"built_in": "Уграђени",
"addons": "Додаци",
"style": "Стил",
"none": "Ниједан",
"search_online_subtitles": "Претражи титлове на мрежи",
"preview": "Преглед",
"quick_presets": "Брза подешавања",
"default": "Подразумевано",
"yellow": "Жуто",
"high_contrast": "Висок контраст",
"large": "Велико",
"core": "Језгро",
"font_size": "Величина фонта",
"show_background": "Прикажи позадину",
"advanced": "Напредно",
"position": "Позиција",
"text_color": "Боја текста",
"align": "Поравнање",
"bottom_offset": "Размак од дна",
"background_opacity": "Провидност позадине",
"text_shadow": "Сенка текста",
"on": "Укључено",
"off": "Искључено",
"outline_color": "Боја оквира",
"outline": "Оквир",
"outline_width": "Ширина оквира",
"letter_spacing": "Размак између слова",
"line_height": "Висина линије",
"timing_offset": "Временски помак (с)",
"visual_sync": "Визуелна синхронизација",
"timing_hint": "Померите титлове раније (-) или касније (+) за синхронизацију.",
"reset_defaults": "Врати на подразумевано"
},
"downloads": {
"title": "Преузимања", "title": "Преузимања",
"no_downloads": "Још нема преузимања", "no_downloads": "Још нема преузимања",
"no_downloads_desc": "Преузети садржај ће се појавити овде за гледање ван мреже", "no_downloads_desc": "Преузети садржај ће се појавити овде за гледање ван мреже",
@ -758,7 +758,65 @@
}, },
"clear_data_desc": "Ово ће ресетовати сва подешавања и обрисати кеш. Да ли сте сигурни?", "clear_data_desc": "Ово ће ресетовати сва подешавања и обрисати кеш. Да ли сте сигурни?",
"app_updates": "Ажурирања апликације", "app_updates": "Ажурирања апликације",
"about_nuvio": "О Nuvio апликацији" "about_nuvio": "О Nuvio апликацији",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Приватност и Подаци", "title": "Приватност и Подаци",

View file

@ -764,7 +764,65 @@
}, },
"clear_data_desc": "Bu işlem tüm ayarları sıfırlayacak ve tüm önbelleği temizleyecektir. Emin misiniz?", "clear_data_desc": "Bu işlem tüm ayarları sıfırlayacak ve tüm önbelleği temizleyecektir. Emin misiniz?",
"app_updates": "Uygulama Güncellemeleri", "app_updates": "Uygulama Güncellemeleri",
"about_nuvio": "Nuvio Hakkında" "about_nuvio": "Nuvio Hakkında",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "Gizlilik ve Veri", "title": "Gizlilik ve Veri",

View file

@ -759,7 +759,65 @@
}, },
"clear_data_desc": "这将重置所有设置并清除所有缓存数据。您确定吗?", "clear_data_desc": "这将重置所有设置并清除所有缓存数据。您确定吗?",
"app_updates": "应用更新", "app_updates": "应用更新",
"about_nuvio": "关于 Nuvio" "about_nuvio": "关于 Nuvio",
"cloud_sync": {
"title": "Nuvio Sync",
"description": "Sync data across your Nuvio devices",
"hero_title": "Cloud Sync",
"hero_subtitle": "Keep your addons, progress, and library aligned across all devices.",
"auth": {
"account": "Account",
"not_configured": "Supabase not configured",
"not_authenticated": "Not authenticated",
"email_session": "Email session",
"signed_in_as": "Signed in as {{email}}",
"not_signed_in": "Not signed in",
"effective_owner": "Effective owner: {{id}}"
},
"stats": {
"title": "Database Statistics",
"plugins": "Plugins",
"addons": "Addons",
"watch_progress": "Watch Progress",
"library_items": "Library Items",
"watched_items": "Watched Items",
"signin_required": "Sign in to load remote data counts."
},
"actions": {
"title": "Actions",
"description": "Pull to update this device from the cloud, or push from this device as the latest source.",
"pull_btn": "Pull from Cloud",
"push_btn": "Push from Device",
"manage_account": "Manage Account",
"sign_out": "Sign Out",
"sign_in_up": "Sign In / Up"
},
"alerts": {
"pull_success_title": "Cloud Data Pulled",
"pull_success_msg": "The latest cloud data has been downloaded to this device.",
"pull_failed_title": "Pull Failed",
"pull_failed_msg": "Failed to download data from the cloud",
"push_success_title": "Push Completed",
"push_success_msg": "Device data has been uploaded to the cloud.",
"push_failed_title": "Push Failed",
"push_failed_msg": "Failed to upload local data",
"sign_out_failed": "Sign Out Failed",
"sign_out_failed_title": "Logout Error"
},
"external_sync": {
"title": "External Sync Priority",
"active_msg": "{{services}} is active. Watch progress and library updates are managed by these services instead of Nuvio cloud database.",
"inactive_msg": "If Trakt or Simkl sync is enabled, watch progress and library updates will use those services instead of Nuvio cloud database."
},
"pre_auth": {
"title": "Before Syncing",
"description": "Sign in to start cloud sync and keep your data consistent across devices.",
"point_1": "• Addons and plugins settings",
"point_2": "• Watch progress and library",
"env_warning": "Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_ANON_KEY to enable sync."
},
"connection": "Connection"
}
}, },
"privacy": { "privacy": {
"title": "隐私和数据", "title": "隐私和数据",

View file

@ -7,6 +7,7 @@ import { catalogService, StreamingContent } from './catalogService';
import { storageService } from './storageService'; import { storageService } from './storageService';
import { watchedService, LocalWatchedItem } from './watchedService'; import { watchedService, LocalWatchedItem } from './watchedService';
import { TraktService } from './traktService'; import { TraktService } from './traktService';
import { SimklService } from './simklService';
const SUPABASE_SESSION_KEY = '@supabase:session'; const SUPABASE_SESSION_KEY = '@supabase:session';
const DEFAULT_SYNC_DEBOUNCE_MS = 2000; const DEFAULT_SYNC_DEBOUNCE_MS = 2000;
@ -123,6 +124,7 @@ class SupabaseSyncService {
private readonly foregroundPullCooldownMs = 30000; private readonly foregroundPullCooldownMs = 30000;
private pendingWatchProgressDeleteKeys = new Set<string>(); private pendingWatchProgressDeleteKeys = new Set<string>();
private watchProgressDeleteTimer: ReturnType<typeof setTimeout> | null = null; private watchProgressDeleteTimer: ReturnType<typeof setTimeout> | null = null;
private watchProgressPushedSignatures = new Map<string, string>();
private pendingPushTimers: Record<PushTarget, ReturnType<typeof setTimeout> | null> = { private pendingPushTimers: Record<PushTarget, ReturnType<typeof setTimeout> | null> = {
plugins: null, plugins: null,
@ -236,6 +238,7 @@ class SupabaseSyncService {
} }
this.session = null; this.session = null;
this.watchProgressPushedSignatures.clear();
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY); await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
} }
@ -277,13 +280,15 @@ class SupabaseSyncService {
await this.pushPluginsFromLocal(); await this.pushPluginsFromLocal();
await this.pushAddonsFromLocal(); await this.pushAddonsFromLocal();
const traktConnected = await this.isTraktConnected(); await this.pushLibraryFromLocal();
if (traktConnected) { const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (externalProgressSyncConnected) {
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items push, keeping library sync.');
logger.log('[SupabaseSyncService] pushAllLocalData: complete');
return; return;
} }
await this.pushWatchProgressFromLocal(); await this.pushWatchProgressFromLocal();
await this.pushLibraryFromLocal();
await this.pushWatchedItemsFromLocal(); await this.pushWatchedItemsFromLocal();
logger.log('[SupabaseSyncService] pushAllLocalData: complete'); logger.log('[SupabaseSyncService] pushAllLocalData: complete');
} }
@ -296,13 +301,14 @@ class SupabaseSyncService {
await this.pullPluginsToLocal(); await this.pullPluginsToLocal();
await this.pullAddonsToLocal(); await this.pullAddonsToLocal();
const traktConnected = await this.isTraktConnected(); await this.pullLibraryToLocal();
if (traktConnected) { const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (externalProgressSyncConnected) {
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items pull, keeping library sync.');
return; return;
} }
await this.pullWatchProgressToLocal(); await this.pullWatchProgressToLocal();
await this.pullLibraryToLocal();
await this.pullWatchedItemsToLocal(); await this.pullWatchedItemsToLocal();
}); });
logger.log('[SupabaseSyncService] pullAllToLocal: complete'); logger.log('[SupabaseSyncService] pullAllToLocal: complete');
@ -493,9 +499,18 @@ class SupabaseSyncService {
logger.warn('[SupabaseSyncService] runStartupSync: one or more pull steps failed; skipped startup push-by-design'); logger.warn('[SupabaseSyncService] runStartupSync: one or more pull steps failed; skipped startup push-by-design');
} }
const traktConnected = await this.isTraktConnected(); const libraryPullOk = await this.safeRun('pull_library', async () => {
if (traktConnected) { await this.withSuppressedPushes(async () => {
logger.log('[SupabaseSyncService] Trakt is connected; skipping progress/library/watched Supabase sync.'); await this.pullLibraryToLocal();
});
});
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (externalProgressSyncConnected) {
logger.log('[SupabaseSyncService] External sync (Trakt/Simkl) is connected; skipping watch progress/watched-items Supabase sync (library still synced).');
if (!libraryPullOk) {
logger.warn('[SupabaseSyncService] runStartupSync: library pull failed while external sync priority is active');
}
return; return;
} }
@ -505,12 +520,6 @@ class SupabaseSyncService {
}); });
}); });
const libraryPullOk = await this.safeRun('pull_library', async () => {
await this.withSuppressedPushes(async () => {
await this.pullLibraryToLocal();
});
});
const watchedPullOk = await this.safeRun('pull_watched_items', async () => { const watchedPullOk = await this.safeRun('pull_watched_items', async () => {
await this.withSuppressedPushes(async () => { await this.withSuppressedPushes(async () => {
await this.pullWatchedItemsToLocal(); await this.pullWatchedItemsToLocal();
@ -629,8 +638,8 @@ class SupabaseSyncService {
return; return;
} }
const traktConnected = await this.isTraktConnected(); const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (traktConnected) return; if (externalProgressSyncConnected) return;
try { try {
logger.log(`[SupabaseSyncService] flushWatchProgressDeletes: deleting ${keys.length} keys`); logger.log(`[SupabaseSyncService] flushWatchProgressDeletes: deleting ${keys.length} keys`);
@ -700,12 +709,12 @@ class SupabaseSyncService {
return; return;
} }
const traktConnected = await this.isTraktConnected();
if (traktConnected) {
return;
}
if (target === 'watch_progress') { if (target === 'watch_progress') {
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (externalProgressSyncConnected) {
logger.log('[SupabaseSyncService] executeScheduledPush: skipping watch_progress due to external sync priority (Trakt/Simkl)');
return;
}
await this.pushWatchProgressFromLocal(); await this.pushWatchProgressFromLocal();
logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`); logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`);
return; return;
@ -716,6 +725,12 @@ class SupabaseSyncService {
return; return;
} }
const externalProgressSyncConnected = await this.isExternalProgressSyncConnected();
if (externalProgressSyncConnected) {
logger.log('[SupabaseSyncService] executeScheduledPush: skipping watched_items due to external sync priority (Trakt/Simkl)');
return;
}
await this.pushWatchedItemsFromLocal(); await this.pushWatchedItemsFromLocal();
logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`); logger.log(`[SupabaseSyncService] executeScheduledPush: target=${target}:done`);
} }
@ -745,6 +760,8 @@ class SupabaseSyncService {
} }
private async setSession(session: SupabaseSession): Promise<void> { private async setSession(session: SupabaseSession): Promise<void> {
// Reset per-entry push cache on session changes to avoid cross-account state bleed.
this.watchProgressPushedSignatures.clear();
this.session = session; this.session = session;
await mmkvStorage.setItem(SUPABASE_SESSION_KEY, JSON.stringify(session)); await mmkvStorage.setItem(SUPABASE_SESSION_KEY, JSON.stringify(session));
} }
@ -775,6 +792,7 @@ class SupabaseSyncService {
} catch (error) { } catch (error) {
logger.error('[SupabaseSyncService] Failed to refresh session:', error); logger.error('[SupabaseSyncService] Failed to refresh session:', error);
this.session = null; this.session = null;
this.watchProgressPushedSignatures.clear();
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY); await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
return false; return false;
} }
@ -791,6 +809,7 @@ class SupabaseSyncService {
} catch (error) { } catch (error) {
logger.error('[SupabaseSyncService] Token refresh failed:', error); logger.error('[SupabaseSyncService] Token refresh failed:', error);
this.session = null; this.session = null;
this.watchProgressPushedSignatures.clear();
await mmkvStorage.removeItem(SUPABASE_SESSION_KEY); await mmkvStorage.removeItem(SUPABASE_SESSION_KEY);
return null; return null;
} }
@ -997,6 +1016,22 @@ class SupabaseSyncService {
}; };
} }
private getWatchProgressEntrySignature(value: { currentTime?: number; duration?: number; lastUpdated?: number }): string {
return [
Number(value.currentTime || 0),
Number(value.duration || 0),
Number(value.lastUpdated || 0),
].join('|');
}
private buildLocalWatchProgressKey(
contentType: 'movie' | 'series',
contentId: string,
episodeId?: string
): string {
return `${contentType}:${contentId}${episodeId ? `:${episodeId}` : ''}`;
}
private toStreamingContent(item: LibraryRow): StreamingContent { private toStreamingContent(item: LibraryRow): StreamingContent {
const type = item.content_type === 'movie' ? 'movie' : 'series'; const type = item.content_type === 'movie' ? 'movie' : 'series';
const posterShape = (item.poster_shape || 'POSTER').toLowerCase() as 'poster' | 'square' | 'landscape'; const posterShape = (item.poster_shape || 'POSTER').toLowerCase() as 'poster' | 'square' | 'landscape';
@ -1037,6 +1072,19 @@ class SupabaseSyncService {
} }
} }
private async isSimklConnected(): Promise<boolean> {
try {
return await SimklService.getInstance().isAuthenticated();
} catch {
return false;
}
}
private async isExternalProgressSyncConnected(): Promise<boolean> {
if (await this.isTraktConnected()) return true;
return await this.isSimklConnected();
}
private async pullPluginsToLocal(): Promise<void> { private async pullPluginsToLocal(): Promise<void> {
const token = await this.getValidAccessToken(); const token = await this.getValidAccessToken();
if (!token) return; if (!token) return;
@ -1254,11 +1302,10 @@ class SupabaseSyncService {
const type = row.content_type === 'movie' ? 'movie' : 'series'; const type = row.content_type === 'movie' ? 'movie' : 'series';
const season = row.season == null ? null : Number(row.season); const season = row.season == null ? null : Number(row.season);
const episode = row.episode == null ? null : Number(row.episode); const episode = row.episode == null ? null : Number(row.episode);
remoteSet.add(`${type}:${row.content_id}:${season ?? ''}:${episode ?? ''}`);
const episodeId = type === 'series' && season != null && episode != null const episodeId = type === 'series' && season != null && episode != null
? `${row.content_id}:${season}:${episode}` ? `${row.content_id}:${season}:${episode}`
: undefined; : undefined;
remoteSet.add(this.buildLocalWatchProgressKey(type, row.content_id, episodeId));
const local = await storageService.getWatchProgress(row.content_id, type, episodeId); const local = await storageService.getWatchProgress(row.content_id, type, episodeId);
const remoteLastWatched = this.normalizeEpochMs(row.last_watched); const remoteLastWatched = this.normalizeEpochMs(row.last_watched);
@ -1284,15 +1331,46 @@ class SupabaseSyncService {
); );
} }
logger.log(`[SupabaseSyncService] pullWatchProgressToLocal: merged ${(rows || []).length} remote entries (no local prune)`); // Remote-first continue watching: remove local entries that no longer exist remotely.
// This intentionally treats the successful remote pull as authoritative.
const allLocal = await storageService.getAllWatchProgress();
let pruned = 0;
for (const [localKey] of Object.entries(allLocal)) {
if (remoteSet.has(localKey)) continue;
const parsed = this.parseWatchProgressKey(localKey);
if (!parsed) continue;
const episodeId = parsed.videoId && parsed.videoId !== parsed.contentId ? parsed.videoId : undefined;
await storageService.removeWatchProgress(parsed.contentId, parsed.contentType, episodeId);
this.watchProgressPushedSignatures.delete(localKey);
pruned += 1;
}
logger.log(`[SupabaseSyncService] pullWatchProgressToLocal: merged=${(rows || []).length} prunedLocalMissing=${pruned}`);
} }
private async pushWatchProgressFromLocal(): Promise<void> { private async pushWatchProgressFromLocal(): Promise<void> {
const all = await storageService.getAllWatchProgress(); const all = await storageService.getAllWatchProgress();
const entries: WatchProgressRow[] = Object.entries(all).reduce<WatchProgressRow[]>((acc, [key, value]) => { const nextSeenKeys = new Set<string>();
const changedEntries: Array<{ key: string; row: WatchProgressRow; signature: string }> = [];
for (const [key, value] of Object.entries(all)) {
nextSeenKeys.add(key);
const signature = this.getWatchProgressEntrySignature(value);
if (this.watchProgressPushedSignatures.get(key) === signature) {
continue;
}
const parsed = this.parseWatchProgressKey(key); const parsed = this.parseWatchProgressKey(key);
if (!parsed) return acc; if (!parsed) {
acc.push({ continue;
}
changedEntries.push({
key,
signature,
row: {
content_id: parsed.contentId, content_id: parsed.contentId,
content_type: parsed.contentType, content_type: parsed.contentType,
video_id: parsed.videoId, video_id: parsed.videoId,
@ -1302,11 +1380,30 @@ class SupabaseSyncService {
duration: this.secondsToMsLong(value.duration), duration: this.secondsToMsLong(value.duration),
last_watched: this.normalizeEpochMs(value.lastUpdated || Date.now()), last_watched: this.normalizeEpochMs(value.lastUpdated || Date.now()),
progress_key: parsed.progressKey, progress_key: parsed.progressKey,
},
}); });
return acc; }
}, []);
await this.callRpc<void>('sync_push_watch_progress', { p_entries: entries }); // Prune signatures for entries no longer present locally (deletes are handled separately).
for (const existingKey of Array.from(this.watchProgressPushedSignatures.keys())) {
if (!nextSeenKeys.has(existingKey)) {
this.watchProgressPushedSignatures.delete(existingKey);
}
}
if (changedEntries.length === 0) {
logger.log('[SupabaseSyncService] pushWatchProgressFromLocal: no changed entries; skipping push');
return;
}
await this.callRpc<void>('sync_push_watch_progress', {
p_entries: changedEntries.map((entry) => entry.row),
});
for (const entry of changedEntries) {
this.watchProgressPushedSignatures.set(entry.key, entry.signature);
}
logger.log(`[SupabaseSyncService] pushWatchProgressFromLocal: pushedChanged=${changedEntries.length} totalLocal=${Object.keys(all).length}`);
} }
private async pullLibraryToLocal(): Promise<void> { private async pullLibraryToLocal(): Promise<void> {