From e3a09164e606a7b30dc4057efbff1e571db2d966 Mon Sep 17 00:00:00 2001 From: cyberalby2 Date: Fri, 6 Mar 2026 19:55:44 +0100 Subject: [PATCH] PlaybackSettings IntroDB Localization Patch --- src/i18n/locales/en.json | 18 +- src/i18n/locales/it.json | 10 +- .../settings/PlaybackSettingsScreen.tsx | 1335 +++++++++-------- 3 files changed, 759 insertions(+), 604 deletions(-) diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index e1a2e4ed..ce1a6a5b 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -697,7 +697,8 @@ "media": "MEDIA", "notifications": "NOTIFICATIONS", "testing": "TESTING", - "danger_zone": "DANGER ZONE" + "danger_zone": "DANGER ZONE", + "introdb_contribution": "INTRODB CONTRIBUTION" }, "items": { "legal": "Legal & Disclaimer", @@ -758,7 +759,12 @@ "reset_campaigns": "Reset Campaigns", "reset_campaigns_desc": "Clear campaign impressions", "clear_all_data": "Clear All Data", - "clear_all_data_desc": "Reset all settings and cached data" + "clear_all_data_desc": "Reset all settings and cached data", + "enable_intro_submission": "Enable Intro Submission", + "enable_intro_submission_desc": "Contribute timestamps to the community", + "introdb_api_key": "INTRODB API KEY", + "introdb_key_placeholder": "Enter your API Key", + "api_key_cleared": "API Key Cleared" }, "options": { "horizontal": "Horizontal", @@ -906,10 +912,10 @@ "confirm_remove_msg": "Are you sure you want to remove your OpenRouter API key? This will disable AI chat features.", "success_removed": "API key removed successfully", "error_remove": "Failed to remove API key", - "model":"Model", - "using":"Using", - "free_routing":"(free automatic routing)", - "paid_model":"Use a custom OpenRouter model ID (useful for paid plans)." + "model": "Model", + "using": "Using", + "free_routing": "(free automatic routing)", + "paid_model": "Use a custom OpenRouter model ID (useful for paid plans)." }, "catalog_settings": { "title": "Catalogs", diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 9da29162..1221aca9 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -696,7 +696,8 @@ "media": "MEDIA", "notifications": "NOTIFICHE", "testing": "TEST", - "danger_zone": "ZONA PERICOLOSA" + "danger_zone": "ZONA PERICOLOSA", + "introdb_contribution": "CONTRIBUTI INTRODB" }, "items": { "legal": "Note Legali & Disclaimer", @@ -757,7 +758,12 @@ "reset_campaigns": "Ripristina Campagne", "reset_campaigns_desc": "Cancella le impressioni delle campagne", "clear_all_data": "Cancella tutti i dati", - "clear_all_data_desc": "Ripristina tutte le impostazioni e i dati memorizzati" + "clear_all_data_desc": "Ripristina tutte le impostazioni e i dati memorizzati", + "enable_intro_submission":"Abilita contributi IntroDB", + "enable_intro_submission_desc":"Contribuisci con i tuoi timestamp", + "introdb_api_key":"CHIAVE API INTRODB", + "introdb_key_placeholder":"Inserisci la tua chiave API", + "api_key_cleared":"Chiave API rimossa" }, "options": { "horizontal": "Orizzontale", diff --git a/src/screens/settings/PlaybackSettingsScreen.tsx b/src/screens/settings/PlaybackSettingsScreen.tsx index ed0abd6b..a31f0624 100644 --- a/src/screens/settings/PlaybackSettingsScreen.tsx +++ b/src/screens/settings/PlaybackSettingsScreen.tsx @@ -1,5 +1,22 @@ -import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react'; -import { View, StyleSheet, ScrollView, StatusBar, Platform, Text, TouchableOpacity, Dimensions, TextInput, ActivityIndicator } from 'react-native'; +import React, { + useState, + useCallback, + useMemo, + useRef, + useEffect, +} from 'react'; +import { + View, + StyleSheet, + ScrollView, + StatusBar, + Platform, + Text, + TouchableOpacity, + Dimensions, + TextInput, + ActivityIndicator, +} from 'react-native'; import { useNavigation, useFocusEffect } from '@react-navigation/native'; import { NavigationProp } from '@react-navigation/native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -7,10 +24,19 @@ import { useTheme } from '../../contexts/ThemeContext'; import { useSettings } from '../../hooks/useSettings'; import { RootStackParamList } from '../../navigation/AppNavigator'; import ScreenHeader from '../../components/common/ScreenHeader'; -import { SettingsCard, SettingItem, CustomSwitch, ChevronRight } from './SettingsComponents'; +import { + SettingsCard, + SettingItem, + CustomSwitch, + ChevronRight, +} from './SettingsComponents'; import { useRealtimeConfig } from '../../hooks/useRealtimeConfig'; import { MaterialIcons } from '@expo/vector-icons'; -import { BottomSheetModal, BottomSheetScrollView, BottomSheetBackdrop } from '@gorhom/bottom-sheet'; +import { + BottomSheetModal, + BottomSheetScrollView, + BottomSheetBackdrop, +} from '@gorhom/bottom-sheet'; import { useTranslation } from 'react-i18next'; import { SvgXml } from 'react-native-svg'; import { toastService } from '../../services/toastService'; @@ -22,523 +48,628 @@ const INTRODB_LOGO_URI = 'https://introdb.app/images/logo-vector.svg'; // Available languages for audio/subtitle selection const AVAILABLE_LANGUAGES = [ - { code: 'en', name: 'English' }, - { code: 'es', name: 'Spanish' }, - { code: 'fr', name: 'French' }, - { code: 'de', name: 'German' }, - { code: 'it', name: 'Italian' }, - { code: 'ja', name: 'Japanese' }, - { code: 'ko', name: 'Korean' }, - { code: 'zh', name: 'Chinese' }, - { code: 'ru', name: 'Russian' }, - { code: 'pt', name: 'Portuguese' }, - { code: 'hi', name: 'Hindi' }, - { code: 'ar', name: 'Arabic' }, - { code: 'nl', name: 'Dutch' }, - { code: 'sv', name: 'Swedish' }, - { code: 'no', name: 'Norwegian' }, - { code: 'fi', name: 'Finnish' }, - { code: 'da', name: 'Danish' }, - { code: 'pl', name: 'Polish' }, - { code: 'tr', name: 'Turkish' }, - { code: 'cs', name: 'Czech' }, - { code: 'hu', name: 'Hungarian' }, - { code: 'el', name: 'Greek' }, - { code: 'th', name: 'Thai' }, - { code: 'vi', name: 'Vietnamese' }, - { code: 'id', name: 'Indonesian' }, - { code: 'ms', name: 'Malay' }, - { code: 'ta', name: 'Tamil' }, - { code: 'te', name: 'Telugu' }, - { code: 'bn', name: 'Bengali' }, - { code: 'uk', name: 'Ukrainian' }, - { code: 'he', name: 'Hebrew' }, - { code: 'fa', name: 'Persian' }, - { code: 'hr', name: 'Croatian' }, - { code: 'sr', name: 'Serbian' }, - { code: 'bg', name: 'Bulgarian' }, - { code: 'sl', name: 'Slovenian' }, - { code: 'mk', name: 'Macedonian' }, - { code: 'fil', name: 'Filipino' }, - { code: 'ro', name: 'Romanian' }, - { code: 'sq', name: 'Albanian' }, - { code: 'ca', name: 'Catalan' }, + { code: 'en', name: 'English' }, + { code: 'es', name: 'Spanish' }, + { code: 'fr', name: 'French' }, + { code: 'de', name: 'German' }, + { code: 'it', name: 'Italian' }, + { code: 'ja', name: 'Japanese' }, + { code: 'ko', name: 'Korean' }, + { code: 'zh', name: 'Chinese' }, + { code: 'ru', name: 'Russian' }, + { code: 'pt', name: 'Portuguese' }, + { code: 'hi', name: 'Hindi' }, + { code: 'ar', name: 'Arabic' }, + { code: 'nl', name: 'Dutch' }, + { code: 'sv', name: 'Swedish' }, + { code: 'no', name: 'Norwegian' }, + { code: 'fi', name: 'Finnish' }, + { code: 'da', name: 'Danish' }, + { code: 'pl', name: 'Polish' }, + { code: 'tr', name: 'Turkish' }, + { code: 'cs', name: 'Czech' }, + { code: 'hu', name: 'Hungarian' }, + { code: 'el', name: 'Greek' }, + { code: 'th', name: 'Thai' }, + { code: 'vi', name: 'Vietnamese' }, + { code: 'id', name: 'Indonesian' }, + { code: 'ms', name: 'Malay' }, + { code: 'ta', name: 'Tamil' }, + { code: 'te', name: 'Telugu' }, + { code: 'bn', name: 'Bengali' }, + { code: 'uk', name: 'Ukrainian' }, + { code: 'he', name: 'Hebrew' }, + { code: 'fa', name: 'Persian' }, + { code: 'hr', name: 'Croatian' }, + { code: 'sr', name: 'Serbian' }, + { code: 'bg', name: 'Bulgarian' }, + { code: 'sl', name: 'Slovenian' }, + { code: 'mk', name: 'Macedonian' }, + { code: 'fil', name: 'Filipino' }, + { code: 'ro', name: 'Romanian' }, + { code: 'sq', name: 'Albanian' }, + { code: 'ca', name: 'Catalan' }, ]; const SUBTITLE_SOURCE_OPTIONS = [ - { value: 'internal', label: 'Internal First', description: 'Prefer embedded subtitles, then external' }, - { value: 'external', label: 'External First', description: 'Prefer addon subtitles, then embedded' }, - { value: 'any', label: 'Any Available', description: 'Use first available subtitle track' }, + { + value: 'internal', + label: 'Internal First', + description: 'Prefer embedded subtitles, then external', + }, + { + value: 'external', + label: 'External First', + description: 'Prefer addon subtitles, then embedded', + }, + { + value: 'any', + label: 'Any Available', + description: 'Use first available subtitle track', + }, ]; // Props for the reusable content component interface PlaybackSettingsContentProps { - isTablet?: boolean; + isTablet?: boolean; } /** * Reusable PlaybackSettingsContent component * Can be used inline (tablets) or wrapped in a screen (mobile) */ -export const PlaybackSettingsContent: React.FC = ({ isTablet = false }) => { - const navigation = useNavigation>(); - const { currentTheme } = useTheme(); - const { settings, updateSetting } = useSettings(); - const { t } = useTranslation(); - const config = useRealtimeConfig(); +export const PlaybackSettingsContent: React.FC< + PlaybackSettingsContentProps +> = ({ isTablet = false }) => { + const navigation = useNavigation>(); + const { currentTheme } = useTheme(); + const { settings, updateSetting } = useSettings(); + const { t } = useTranslation(); + const config = useRealtimeConfig(); - const [introDbLogoXml, setIntroDbLogoXml] = useState(null); - const [apiKeyInput, setApiKeyInput] = useState(settings?.introDbApiKey || ''); - const [isVerifyingKey, setIsVerifyingKey] = useState(false); + const [introDbLogoXml, setIntroDbLogoXml] = useState(null); + const [apiKeyInput, setApiKeyInput] = useState(settings?.introDbApiKey || ''); + const [isVerifyingKey, setIsVerifyingKey] = useState(false); - const isMounted = useRef(true); + const isMounted = useRef(true); - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); - useEffect(() => { - setApiKeyInput(settings?.introDbApiKey || ''); - }, [settings?.introDbApiKey]); + useEffect(() => { + setApiKeyInput(settings?.introDbApiKey || ''); + }, [settings?.introDbApiKey]); - const handleApiKeySubmit = async () => { - if (!apiKeyInput.trim()) { - updateSetting('introDbApiKey', ''); - toastService.success(t('settings.items.api_key_cleared', { defaultValue: 'API Key Cleared' })); - return; - } + const handleApiKeySubmit = async () => { + if (!apiKeyInput.trim()) { + updateSetting('introDbApiKey', ''); + toastService.success( + t('settings.items.api_key_cleared'), + ); + return; + } - setIsVerifyingKey(true); - const isValid = await introService.verifyApiKey(apiKeyInput); - - if (!isMounted.current) return; - setIsVerifyingKey(false); + setIsVerifyingKey(true); + const isValid = await introService.verifyApiKey(apiKeyInput); - if (isValid) { - updateSetting('introDbApiKey', apiKeyInput); - toastService.success(t('settings.items.api_key_saved', { defaultValue: 'API Key Saved' })); - } else { - toastService.error(t('settings.items.api_key_invalid', { defaultValue: 'Invalid API Key' })); - } - }; + if (!isMounted.current) return; + setIsVerifyingKey(false); - useEffect(() => { - let cancelled = false; - const load = async () => { - try { - const res = await fetch(INTRODB_LOGO_URI); - let xml = await res.text(); - // Inline CSS class-based styles because react-native-svg doesn't support