diff --git a/src/i18n/locales/ar.json b/src/i18n/locales/ar.json
index a9f5002..e96a208 100644
--- a/src/i18n/locales/ar.json
+++ b/src/i18n/locales/ar.json
@@ -1239,5 +1239,87 @@
"dmca_text": "نحن نحترم حقوق الملكية الفكرية للآخرين. نظرًا لأن Nuvio لا يستضيف أي محتوى، فلا يمكننا إزالة المحتوى من الإنترنت. ومع ذلك، إذا كنت تعتقد أن واجهة التطبيق نفسها تنتهك حقوقك، فيرجى الاتصال بنا.",
"warranty_title": "لا يوجد ضمان",
"warranty_text": "يتم توفير هذا البرنامج \"كما هو\"، دون أي ضمان من أي نوع، صريحًا أو ضمنيًا. لا يتحمل المؤلفون أو أصحاب حقوق الطبع والنشر بأي حال من الأحوال المسؤولية عن أي مطالبة أو أضرار أو مسؤولية أخرى تنشأ عن استخدام هذا البرنامج."
+ },
+ "plugin_tester": {
+ "title": "مختبر الإضافات",
+ "subtitle": "تشغيل الكاشطات وفحص السجلات في الوقت الفعلي",
+ "tabs": {
+ "individual": "فردي",
+ "repo": "مختبر المستودع",
+ "code": "الكود",
+ "logs": "السجلات",
+ "results": "النتائج"
+ },
+ "common": {
+ "error": "خطأ",
+ "success": "نجاح",
+ "movie": "فيلم",
+ "tv": "تلفاز",
+ "tmdb_id": "معرف TMDB",
+ "season": "الموسم",
+ "episode": "الحلقة",
+ "running": "جاري التشغيل...",
+ "run_test": "تشغيل الاختبار",
+ "play": "تشغيل",
+ "done": "تم",
+ "test": "اختبار",
+ "testing": "جاري الاختبار..."
+ },
+ "individual": {
+ "load_from_url": "تحميل من الرابط",
+ "load_from_url_desc": "الصق رابط GitHub الخام أو IP محلي واضغط تحميل.",
+ "enter_url_error": "يرجى إدخال رابط",
+ "code_loaded": "تم تحميل الكود من الرابط",
+ "fetch_error": "فشل الجلب: {{message}}",
+ "no_code_error": "لا يوجد كود للتشغيل",
+ "plugin_code": "كود الإضافة",
+ "focus_editor": "توسيع المحرر",
+ "code_placeholder": "// الصق كود الإضافة هنا...",
+ "test_parameters": "معلمات الاختبار",
+ "no_logs": "لا توجد سجلات. شغل اختباراً لرؤية النتائج.",
+ "no_streams": "لم يتم العثور على بث.",
+ "streams_found": "{{count}} بث وجد",
+ "streams_found_plural": "{{count}} بث وجد",
+ "tap_play_hint": "اضغط تشغيل لاختبار البث في المشغل.",
+ "unnamed_stream": "بث بدون اسم",
+ "quality": "الجودة: {{quality}}",
+ "size": "الحجم: {{size}}",
+ "url_label": "الرابط: {{url}}",
+ "headers_info": "الرؤوس: {{count}} رأس مخصص",
+ "find_placeholder": "بحث في الكود...",
+ "edit_code_title": "تعديل الكود",
+ "no_url_stream_error": "لا يوجد رابط لهذا البث"
+ },
+ "repo": {
+ "title": "مختبر المستودع",
+ "description": "جلب مستودع (رابط محلي أو GitHub خام) واختبار كل مزود.",
+ "enter_repo_url_error": "يرجى إدخال رابط المستودع",
+ "invalid_url_title": "رابط غير صالح",
+ "invalid_url_msg": "استخدم رابط GitHub خام أو رابط محلي http(s).\n\nمثال:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "تعذر إنشاء رابط البيان من المدخلات",
+ "manifest_fetch_error": "فشل جلب البيان",
+ "repo_manifest_fetch_error": "فشل جلب بيان المستودع",
+ "missing_filename": "اسم الملف مفقود في البيان",
+ "scraper_build_error": "تعذر إنشاء رابط الكاشط",
+ "download_scraper_error": "فشل تحميل الكاشط",
+ "test_failed": "فشل الاختبار",
+ "test_parameters": "معلمات اختبار المستودع",
+ "test_parameters_desc": "هذه المعلمات تستخدم فقط لمختبر المستودع.",
+ "using_info": "باستخدام: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "باستخدام: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "المزودون",
+ "repository_default": "المستودع",
+ "providers_count": "{{count}} مزود",
+ "fetch_hint": "جلب مستودع لعرض المزودين.",
+ "test_all": "اختبار الكل",
+ "status_running": "جاري التشغيل",
+ "status_ok": "نجاح ({{count}})",
+ "status_ok_empty": "نجاح (0)",
+ "status_failed": "فشل",
+ "status_idle": "خامل",
+ "tried_url": "تمت المحاولة: {{url}}",
+ "provider_logs": "سجلات المزود",
+ "no_logs_captured": "لم يتم التقاط سجلات."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json
index ae4c8c9..de5ec11 100644
--- a/src/i18n/locales/de.json
+++ b/src/i18n/locales/de.json
@@ -1085,5 +1085,87 @@
"dmca_text": "Wir respektieren die geistigen Eigentumsrechte anderer.",
"warranty_title": "Keine Garantie",
"warranty_text": "Diese Software wird ohne Mängelgewähr bereitgestellt."
+ },
+ "plugin_tester": {
+ "title": "Module Tester",
+ "subtitle": "Scraper ausführen und Logs in Echtzeit prüfen",
+ "tabs": {
+ "individual": "Einzeln",
+ "repo": "Repo Tester",
+ "code": "Code",
+ "logs": "Logs",
+ "results": "Ergebnisse"
+ },
+ "common": {
+ "error": "Fehler",
+ "success": "Erfolg",
+ "movie": "Film",
+ "tv": "TV",
+ "tmdb_id": "TMDB ID",
+ "season": "Staffel",
+ "episode": "Episode",
+ "running": "Läuft…",
+ "run_test": "Test Starten",
+ "play": "Abspielen",
+ "done": "Fertig",
+ "test": "Testen",
+ "testing": "Testet…"
+ },
+ "individual": {
+ "load_from_url": "Von URL laden",
+ "load_from_url_desc": "GitHub Raw-URL oder lokale IP einfügen und laden.",
+ "enter_url_error": "Bitte URL eingeben",
+ "code_loaded": "Code von URL geladen",
+ "fetch_error": "Fehler beim Laden: {{message}}",
+ "no_code_error": "Kein Code zum Ausführen",
+ "plugin_code": "Plugin Code",
+ "focus_editor": "Editor fokussieren",
+ "code_placeholder": "// Plugin-Code hier einfügen...",
+ "test_parameters": "Test-Parameter",
+ "no_logs": "Keine Logs. Test ausführen für Ausgabe.",
+ "no_streams": "Keine Streams gefunden.",
+ "streams_found": "{{count}} Stream Gefunden",
+ "streams_found_plural": "{{count}} Streams Gefunden",
+ "tap_play_hint": "Abspielen tippen, um im nativen Player zu testen.",
+ "unnamed_stream": "Unbenannter Stream",
+ "quality": "Qualität: {{quality}}",
+ "size": "Größe: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Headers: {{count}} eigene(r) Header",
+ "find_placeholder": "Im Code suchen…",
+ "edit_code_title": "Code bearbeiten",
+ "no_url_stream_error": "Keine URL für diesen Stream gefunden"
+ },
+ "repo": {
+ "title": "Repo Tester",
+ "description": "Repository abrufen (lokal oder GitHub) und alle Provider testen.",
+ "enter_repo_url_error": "Bitte Repository-URL eingeben",
+ "invalid_url_title": "Ungültige URL",
+ "invalid_url_msg": "Benutze eine GitHub Raw-URL oder lokale http(s) URL.\n\nBeispiel:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Manifest-URL konnte nicht erstellt werden",
+ "manifest_fetch_error": "Manifest konnte nicht geladen werden",
+ "repo_manifest_fetch_error": "Repo-Manifest fehlgeschlagen",
+ "missing_filename": "Dateiname fehlt im Manifest",
+ "scraper_build_error": "Scraper-URL konnte nicht erstellt werden",
+ "download_scraper_error": "Scraper-Download fehlgeschlagen",
+ "test_failed": "Test fehlgeschlagen",
+ "test_parameters": "Repo Test-Parameter",
+ "test_parameters_desc": "Diese Parameter gelten nur für den Repo Tester.",
+ "using_info": "Nutzt: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Nutzt: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Provider",
+ "repository_default": "Repository",
+ "providers_count": "{{count}} Provider",
+ "fetch_hint": "Repo laden um Provider zu zeigen.",
+ "test_all": "Alle Testen",
+ "status_running": "LÄUFT",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FEHLER",
+ "status_idle": "IDLE",
+ "tried_url": "Versucht: {{url}}",
+ "provider_logs": "Provider Logs",
+ "no_logs_captured": "Keine Logs erfasst."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 92b8909..ef0b00f 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -1242,5 +1242,87 @@
"dmca_text": "We respect the intellectual property rights of others. Since Nuvio does not host any content, we cannot remove content from the internet. However, if you believe that the application interface itself infringes on your rights, please contact us.",
"warranty_title": "No Warranty",
"warranty_text": "This software is provided \"as is\", without warranty of any kind, express or implied. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability arising from the use of this software."
+ },
+ "plugin_tester": {
+ "title": "Plugin Tester",
+ "subtitle": "Run scrapers and inspect logs in real-time",
+ "tabs": {
+ "individual": "Individual",
+ "repo": "Repo Tester",
+ "code": "Code",
+ "logs": "Logs",
+ "results": "Results"
+ },
+ "common": {
+ "error": "Error",
+ "success": "Success",
+ "movie": "Movie",
+ "tv": "TV",
+ "tmdb_id": "TMDB ID",
+ "season": "Season",
+ "episode": "Episode",
+ "running": "Running…",
+ "run_test": "Run Test",
+ "play": "Play",
+ "done": "Done",
+ "test": "Test",
+ "testing": "Testing…"
+ },
+ "individual": {
+ "load_from_url": "Load from URL",
+ "load_from_url_desc": "Paste a raw GitHub URL or local IP and tap download.",
+ "enter_url_error": "Please enter a URL",
+ "code_loaded": "Code loaded from URL",
+ "fetch_error": "Failed to fetch: {{message}}",
+ "no_code_error": "No code to run",
+ "plugin_code": "Plugin Code",
+ "focus_editor": "Focus code editor",
+ "code_placeholder": "// Paste plugin code here...",
+ "test_parameters": "Test Parameters",
+ "no_logs": "No logs yet. Run a test to see output.",
+ "no_streams": "No streams found yet.",
+ "streams_found": "{{count}} Stream Found",
+ "streams_found_plural": "{{count}} Streams Found",
+ "tap_play_hint": "Tap Play to test a stream in the native player.",
+ "unnamed_stream": "Unnamed Stream",
+ "quality": "Quality: {{quality}}",
+ "size": "Size: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Headers: {{count}} custom header(s)",
+ "find_placeholder": "Find in code…",
+ "edit_code_title": "Edit Code",
+ "no_url_stream_error": "No URL found for this stream"
+ },
+ "repo": {
+ "title": "Repo Tester",
+ "description": "Fetch a repository (local URL or GitHub raw) and test each provider.",
+ "enter_repo_url_error": "Please enter a repository URL",
+ "invalid_url_title": "Invalid URL",
+ "invalid_url_msg": "Use a GitHub raw URL or a local http(s) URL.\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Could not build a manifest URL from the input",
+ "manifest_fetch_error": "Failed to fetch manifest",
+ "repo_manifest_fetch_error": "Failed to fetch repository manifest",
+ "missing_filename": "Missing filename in manifest",
+ "scraper_build_error": "Could not build a scraper URL",
+ "download_scraper_error": "Failed to download scraper",
+ "test_failed": "Test failed",
+ "test_parameters": "Repo Test Parameters",
+ "test_parameters_desc": "These parameters are used only for Repo Tester.",
+ "using_info": "Using: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Using: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Providers",
+ "repository_default": "Repository",
+ "providers_count": "{{count}} providers",
+ "fetch_hint": "Fetch a repo to list providers.",
+ "test_all": "Test All",
+ "status_running": "RUNNING",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FAILED",
+ "status_idle": "IDLE",
+ "tried_url": "Tried: {{url}}",
+ "provider_logs": "Provider Logs",
+ "no_logs_captured": "No logs captured."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json
index 39b12c0..abcf989 100644
--- a/src/i18n/locales/es.json
+++ b/src/i18n/locales/es.json
@@ -1238,6 +1238,88 @@
"dmca_title": "Derechos de autor y DMCA",
"dmca_text": "Respetamos los derechos de propiedad intelectual de otros. Dado que Nuvio no aloja ningún contenido, no podemos eliminar contenido de Internet. Sin embargo, si crees que la interfaz de la aplicación en sí infringe tus derechos, por favor contáctanos.",
"warranty_title": "Sin garantía",
- "warranty_text": "Este software se proporciona \"tal cual\", sin garantía de ningún tipo, expresa o implícita. En ningún caso los autores o titulares de los derechos de autor serán responsables de ninguna reclamación, daños u otra responsabilidad derivada del uso de este software."
+ "warranty_text": "Este software se proporciona \"tal cual\", sin garantía de ningún tipo, expresa o implícita. En ningún caso los autores o titulares de los derechos de autor serán responsables de ninguna reclamación, daños u otra responsabilidad que surja del uso de este software."
+ },
+ "plugin_tester": {
+ "title": "Probador de Plugins",
+ "subtitle": "Ejecuta scrapers e inspecciona logs en tiempo real",
+ "tabs": {
+ "individual": "Individual",
+ "repo": "Probador de Repo",
+ "code": "Código",
+ "logs": "Registros",
+ "results": "Resultados"
+ },
+ "common": {
+ "error": "Error",
+ "success": "Éxito",
+ "movie": "Película",
+ "tv": "TV",
+ "tmdb_id": "ID de TMDB",
+ "season": "Temporada",
+ "episode": "Episodio",
+ "running": "Ejecutando…",
+ "run_test": "Ejecutar Prueba",
+ "play": "Reproducir",
+ "done": "Listo",
+ "test": "Probar",
+ "testing": "Probando…"
+ },
+ "individual": {
+ "load_from_url": "Cargar desde URL",
+ "load_from_url_desc": "Pega una URL raw de GitHub o IP local y toca descargar.",
+ "enter_url_error": "Por favor ingresa una URL",
+ "code_loaded": "Código cargado desde URL",
+ "fetch_error": "Error al obtener: {{message}}",
+ "no_code_error": "No hay código para ejecutar",
+ "plugin_code": "Código del Plugin",
+ "focus_editor": "Enfocar editor",
+ "code_placeholder": "// Pega el código del plugin aquí...",
+ "test_parameters": "Parámetros de Prueba",
+ "no_logs": "Sin registros. Ejecuta una prueba para ver la salida.",
+ "no_streams": "No se encontraron streams.",
+ "streams_found": "{{count}} Stream Encontrado",
+ "streams_found_plural": "{{count}} Streams Encontrados",
+ "tap_play_hint": "Toca Reproducir para probar en el reproductor nativo.",
+ "unnamed_stream": "Stream Sin Nombre",
+ "quality": "Calidad: {{quality}}",
+ "size": "Tamaño: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Headers: {{count}} encabezado(s) personalizado(s)",
+ "find_placeholder": "Buscar en código…",
+ "edit_code_title": "Editar Código",
+ "no_url_stream_error": "No se encontró URL para este stream"
+ },
+ "repo": {
+ "title": "Probador de Repo",
+ "description": "Obtén un repositorio (URL local o GitHub raw) y prueba cada proveedor.",
+ "enter_repo_url_error": "Por favor ingresa una URL del repositorio",
+ "invalid_url_title": "URL Inválida",
+ "invalid_url_msg": "Usa una URL raw de GitHub o una URL local http(s).\n\nEjemplo:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "No se pudo construir una URL de manifiesto desde la entrada",
+ "manifest_fetch_error": "Error al obtener manifiesto",
+ "repo_manifest_fetch_error": "Error al obtener manifiesto del repositorio",
+ "missing_filename": "Falta nombre de archivo en manifiesto",
+ "scraper_build_error": "No se pudo construir una URL de scraper",
+ "download_scraper_error": "Error al descargar scraper",
+ "test_failed": "Prueba fallida",
+ "test_parameters": "Parámetros de Prueba de Repo",
+ "test_parameters_desc": "Estos parámetros se usan solo para el Probador de Repo.",
+ "using_info": "Usando: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Usando: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Proveedores",
+ "repository_default": "Repositorio",
+ "providers_count": "{{count}} proveedores",
+ "fetch_hint": "Obtén un repo para listar proveedores.",
+ "test_all": "Probar Todo",
+ "status_running": "EJECUTANDO",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FALLÓ",
+ "status_idle": "INACTIVO",
+ "tried_url": "Intentado: {{url}}",
+ "provider_logs": "Registros del Proveedor",
+ "no_logs_captured": "No se capturaron registros."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index ac8cef6..1a08038 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -1239,5 +1239,87 @@
"dmca_text": "Nous respectons les droits de propriété intellectuelle d'autrui. Étant donné que Nuvio n'héberge aucun contenu, nous ne pouvons pas supprimer de contenu d'Internet. Toutefois, si vous pensez que l'interface de l'application elle-même enfreint vos droits, veuillez nous contacter.",
"warranty_title": "Aucune Garantie",
"warranty_text": "Ce logiciel est fourni \"tel quel\", sans garantie d'aucune sorte, expresse ou implicite. En aucun cas, les auteurs ou titulaires de droits d'auteur ne pourront être tenus responsables de toute réclamation, dommage ou autre responsabilité découlant de l'utilisation de ce logiciel."
+ },
+ "plugin_tester": {
+ "title": "Testeur de Plugin",
+ "subtitle": "Exécuter des scrapers et inspecter les logs en temps réel",
+ "tabs": {
+ "individual": "Individuel",
+ "repo": "Testeur de Dépôt",
+ "code": "Code",
+ "logs": "Logs",
+ "results": "Résultats"
+ },
+ "common": {
+ "error": "Erreur",
+ "success": "Succès",
+ "movie": "Film",
+ "tv": "Série TV",
+ "tmdb_id": "ID TMDB",
+ "season": "Saison",
+ "episode": "Épisode",
+ "running": "En cours…",
+ "run_test": "Lancer le Test",
+ "play": "Lire",
+ "done": "Terminé",
+ "test": "Tester",
+ "testing": "Test en cours…"
+ },
+ "individual": {
+ "load_from_url": "Charger depuis URL",
+ "load_from_url_desc": "Collez une URL GitHub raw ou IP locale et appuyez sur télécharger.",
+ "enter_url_error": "Veuillez entrer une URL",
+ "code_loaded": "Code chargé depuis l'URL",
+ "fetch_error": "Échec de récupération : {{message}}",
+ "no_code_error": "Aucun code à exécuter",
+ "plugin_code": "Code du Plugin",
+ "focus_editor": "Focus éditeur",
+ "code_placeholder": "// Collez le code du plugin ici...",
+ "test_parameters": "Paramètres de Test",
+ "no_logs": "Aucun log. Lancez un test pour voir la sortie.",
+ "no_streams": "Aucun flux trouvé.",
+ "streams_found": "{{count}} Flux Trouvé",
+ "streams_found_plural": "{{count}} Flux Trouvés",
+ "tap_play_hint": "Appuyez sur Lire pour tester un flux dans le lecteur natif.",
+ "unnamed_stream": "Flux Sans Nom",
+ "quality": "Qualité : {{quality}}",
+ "size": "Taille : {{size}}",
+ "url_label": "URL : {{url}}",
+ "headers_info": "En-têtes : {{count}} en-tête(s) personnalisé(s)",
+ "find_placeholder": "Chercher dans le code…",
+ "edit_code_title": "Éditer le Code",
+ "no_url_stream_error": "Aucune URL trouvée pour ce flux"
+ },
+ "repo": {
+ "title": "Testeur de Dépôt",
+ "description": "Récupérez un dépôt (URL locale ou GitHub raw) et testez chaque fournisseur.",
+ "enter_repo_url_error": "Veuillez entrer une URL de dépôt",
+ "invalid_url_title": "URL Invalide",
+ "invalid_url_msg": "Utilisez une URL GitHub raw ou une URL locale http(s).\n\nExemple :\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Impossible de construire une URL de manifeste à partir de l'entrée",
+ "manifest_fetch_error": "Échec de récupération du manifeste",
+ "repo_manifest_fetch_error": "Échec de récupération du manifeste du dépôt",
+ "missing_filename": "Nom de fichier manquant dans le manifeste",
+ "scraper_build_error": "Impossible de construire une URL de scraper",
+ "download_scraper_error": "Échec de téléchargement du scraper",
+ "test_failed": "Test échoué",
+ "test_parameters": "Paramètres de Test de Dépôt",
+ "test_parameters_desc": "Ces paramètres sont utilisés uniquement pour le Testeur de Dépôt.",
+ "using_info": "Utilisatin : {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Utilisation : {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Fournisseurs",
+ "repository_default": "Dépôt",
+ "providers_count": "{{count}} fournisseurs",
+ "fetch_hint": "Récupérez un dépôt pour lister les fournisseurs.",
+ "test_all": "Tout Tester",
+ "status_running": "EN COURS",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "ÉCHEC",
+ "status_idle": "INACTIF",
+ "tried_url": "Essayé : {{url}}",
+ "provider_logs": "Logs du Fournisseur",
+ "no_logs_captured": "Aucun log capturé."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json
index caddec1..a154b7f 100644
--- a/src/i18n/locales/it.json
+++ b/src/i18n/locales/it.json
@@ -1237,5 +1237,87 @@
"dmca_text": "Rispettiamo i diritti di proprietà intellettuale altrui. Poiché Nuvio non ospita alcun contenuto, non possiamo rimuovere contenuti da Internet. Tuttavia, se ritieni che l'interfaccia stessa dell'applicazione violi i tuoi diritti, ti preghiamo di contattarci.",
"warranty_title": "Nessuna Garanzia",
"warranty_text": "Questo software è fornito \"così com'è\", senza garanzia di alcun tipo, espressa o implicita. In nessun caso gli autori o i detentori del copyright saranno responsabili per qualsiasi reclamo, danno o altra responsabilità derivante dall'uso di questo software."
+ },
+ "plugin_tester": {
+ "title": "Tester Plugin",
+ "subtitle": "Esegui scraper e ispeziona log in tempo reale",
+ "tabs": {
+ "individual": "Individuale",
+ "repo": "Tester Repo",
+ "code": "Codice",
+ "logs": "Log",
+ "results": "Risultati"
+ },
+ "common": {
+ "error": "Errore",
+ "success": "Successo",
+ "movie": "Film",
+ "tv": "Serie TV",
+ "tmdb_id": "ID TMDB",
+ "season": "Stagione",
+ "episode": "Episodio",
+ "running": "In esecuzione…",
+ "run_test": "Esegui Test",
+ "play": "Riproduci",
+ "done": "Fatto",
+ "test": "Test",
+ "testing": "Test in corso…"
+ },
+ "individual": {
+ "load_from_url": "Carica da URL",
+ "load_from_url_desc": "Incolla un URL GitHub raw o IP locale e tocca scarica.",
+ "enter_url_error": "Inserisci un URL",
+ "code_loaded": "Codice caricato da URL",
+ "fetch_error": "Caricamento fallito: {{message}}",
+ "no_code_error": "Nessun codice da eseguire",
+ "plugin_code": "Codice Plugin",
+ "focus_editor": "Focus editor",
+ "code_placeholder": "// Incolla il codice del plugin qui...",
+ "test_parameters": "Parametri di Test",
+ "no_logs": "Nessun log. Esegui un test per vedere l'output.",
+ "no_streams": "Nessun stream trovato.",
+ "streams_found": "{{count}} Stream Trovato",
+ "streams_found_plural": "{{count}} Stream Trovati",
+ "tap_play_hint": "Tocca Riproduci per testare uno stream nel player nativo.",
+ "unnamed_stream": "Stream Senza Nome",
+ "quality": "Qualità: {{quality}}",
+ "size": "Dimensione: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Header: {{count}} header personalizzati",
+ "find_placeholder": "Cerca nel codice…",
+ "edit_code_title": "Modifica Codice",
+ "no_url_stream_error": "Nessun URL trovato per questo stream"
+ },
+ "repo": {
+ "title": "Tester Repo",
+ "description": "Recupera un repository (URL locale o GitHub raw) e testa ogni provider.",
+ "enter_repo_url_error": "Inserisci un URL del repository",
+ "invalid_url_title": "URL Non Valido",
+ "invalid_url_msg": "Usa un URL GitHub raw o un URL locale http(s).\n\nEsempio:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Impossibile creare un URL manifest dall'input",
+ "manifest_fetch_error": "Caricamento manifest fallito",
+ "repo_manifest_fetch_error": "Caricamento manifest repo fallito",
+ "missing_filename": "Nome file mancante nel manifest",
+ "scraper_build_error": "Impossibile creare un URL scraper",
+ "download_scraper_error": "Download scraper fallito",
+ "test_failed": "Test fallito",
+ "test_parameters": "Parametri Test Repo",
+ "test_parameters_desc": "Questi parametri sono usati solo per il Tester Repo.",
+ "using_info": "Uso: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Uso: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Provider",
+ "repository_default": "Repository",
+ "providers_count": "{{count}} provider",
+ "fetch_hint": "Recupera un repo per elencare i provider.",
+ "test_all": "Testa Tutto",
+ "status_running": "IN ESECUZIONE",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FALLITO",
+ "status_idle": "INATTIVO",
+ "tried_url": "Provato: {{url}}",
+ "provider_logs": "Log Provider",
+ "no_logs_captured": "Nessun log catturato."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/pt-BR.json b/src/i18n/locales/pt-BR.json
index 6516c0f..acaceb4 100644
--- a/src/i18n/locales/pt-BR.json
+++ b/src/i18n/locales/pt-BR.json
@@ -1208,5 +1208,87 @@
"dmca_text": "Respeitamos os direitos de propriedade intelectual de terceiros. Como o Nuvio não hospeda nenhum conteúdo, não podemos remover conteúdo da internet. No entanto, se você acredita que a interface do aplicativo em si infringe seus direitos, entre em contato conosco.",
"warranty_title": "Sem Garantia",
"warranty_text": "Este software é fornecido \"como está\", sem garantia de qualquer tipo, expressa ou implícita. Em nenhum caso os autores ou detentores de direitos autorais serão responsáveis por qualquer reclamação, danos ou outra responsabilidade decorrente do uso deste software."
+ },
+ "plugin_tester": {
+ "title": "Testador de Plugin",
+ "subtitle": "Execute scrapers e inspecione logs em tempo real",
+ "tabs": {
+ "individual": "Individual",
+ "repo": "Testador de Repo",
+ "code": "Código",
+ "logs": "Logs",
+ "results": "Resultados"
+ },
+ "common": {
+ "error": "Erro",
+ "success": "Sucesso",
+ "movie": "Filme",
+ "tv": "TV",
+ "tmdb_id": "ID TMDB",
+ "season": "Temporada",
+ "episode": "Episódio",
+ "running": "Executando…",
+ "run_test": "Executar Teste",
+ "play": "Reproduzir",
+ "done": "Concluído",
+ "test": "Testar",
+ "testing": "Testando…"
+ },
+ "individual": {
+ "load_from_url": "Carregar da URL",
+ "load_from_url_desc": "Cole uma URL raw do GitHub ou IP local e toque em baixar.",
+ "enter_url_error": "Por favor, insira uma URL",
+ "code_loaded": "Código carregado da URL",
+ "fetch_error": "Falha ao buscar: {{message}}",
+ "no_code_error": "Sem código para executar",
+ "plugin_code": "Código do Plugin",
+ "focus_editor": "Focar editor",
+ "code_placeholder": "// Cole o código do plugin aqui...",
+ "test_parameters": "Parâmetros de Teste",
+ "no_logs": "Sem logs. Execute um teste para ver a saída.",
+ "no_streams": "Nenhum stream encontrado.",
+ "streams_found": "{{count}} Stream Encontrado",
+ "streams_found_plural": "{{count}} Streams Encontrados",
+ "tap_play_hint": "Toque em Reproduzir para testar um stream no player nativo.",
+ "unnamed_stream": "Stream Sem Nome",
+ "quality": "Qualidade: {{quality}}",
+ "size": "Tamanho: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Headers: {{count}} cabeçalho(s) personalizado(s)",
+ "find_placeholder": "Buscar no código…",
+ "edit_code_title": "Editar Código",
+ "no_url_stream_error": "Nenhuma URL encontrada para este stream"
+ },
+ "repo": {
+ "title": "Testador de Repo",
+ "description": "Busque um repositório (URL local ou GitHub raw) e teste cada provedor.",
+ "enter_repo_url_error": "Por favor, insira uma URL de repositório",
+ "invalid_url_title": "URL Inválida",
+ "invalid_url_msg": "Use uma URL raw do GitHub ou uma URL local http(s).\n\nExemplo:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Não foi possível criar uma URL de manifesto a partir da entrada",
+ "manifest_fetch_error": "Falha ao buscar manifesto",
+ "repo_manifest_fetch_error": "Falha ao buscar manifesto do repositório",
+ "missing_filename": "Nome de arquivo ausente no manifesto",
+ "scraper_build_error": "Não foi possível criar uma URL de scraper",
+ "download_scraper_error": "Falha ao baixar scraper",
+ "test_failed": "Teste falhou",
+ "test_parameters": "Parâmetros de Teste de Repo",
+ "test_parameters_desc": "Estes parâmetros são usados apenas para o Testador de Repo.",
+ "using_info": "Usando: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "Usando: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Provedores",
+ "repository_default": "Repositório",
+ "providers_count": "{{count}} provedores",
+ "fetch_hint": "Busque um repo para listar provedores.",
+ "test_all": "Testar Tudo",
+ "status_running": "EXECUTANDO",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FALHOU",
+ "status_idle": "INATIVO",
+ "tried_url": "Tentado: {{url}}",
+ "provider_logs": "Logs do Provedor",
+ "no_logs_captured": "Nenhum log capturado."
+ }
}
}
\ No newline at end of file
diff --git a/src/i18n/locales/pt-PT.json b/src/i18n/locales/pt-PT.json
index 700984d..da57667 100644
--- a/src/i18n/locales/pt-PT.json
+++ b/src/i18n/locales/pt-PT.json
@@ -1208,5 +1208,87 @@
"dmca_text": "Respeitamos os direitos de propriedade intelectual de terceiros. Como o Nuvio não hospeda nenhum conteúdo, não podemos remover conteúdo da internet. No entanto, se você acredita que a interface do aplicativo em si infringe seus direitos, entre em contato conosco.",
"warranty_title": "Sem Garantia",
"warranty_text": "Este software é fornecido \"como está\", sem garantia de qualquer tipo, expressa ou implícita. Em nenhum caso os autores ou detentores de direitos autorais serão responsáveis por qualquer reclamação, danos ou outra responsabilidade decorrente do uso deste software."
+ },
+ "plugin_tester": {
+ "title": "Testador de Plugin",
+ "subtitle": "Execute scrapers e inspecione logs em tempo real",
+ "tabs": {
+ "individual": "Individual",
+ "repo": "Testador de Repo",
+ "code": "Código",
+ "logs": "Logs",
+ "results": "Resultados"
+ },
+ "common": {
+ "error": "Erro",
+ "success": "Sucesso",
+ "movie": "Filme",
+ "tv": "TV",
+ "tmdb_id": "ID TMDB",
+ "season": "Temporada",
+ "episode": "Episódio",
+ "running": "A executar…",
+ "run_test": "Executar Teste",
+ "play": "Reproduzir",
+ "done": "Concluído",
+ "test": "Testar",
+ "testing": "A testar…"
+ },
+ "individual": {
+ "load_from_url": "Carregar de URL",
+ "load_from_url_desc": "Cole um URL raw do GitHub ou IP local e toque em descarregar.",
+ "enter_url_error": "Por favor, insira um URL",
+ "code_loaded": "Código carregado de URL",
+ "fetch_error": "Falha ao obter: {{message}}",
+ "no_code_error": "Sem código para executar",
+ "plugin_code": "Código do Plugin",
+ "focus_editor": "Focar editor",
+ "code_placeholder": "// Cole o código do plugin aqui...",
+ "test_parameters": "Parâmetros de Teste",
+ "no_logs": "Sem logs. Execute um teste para ver a saída.",
+ "no_streams": "Nenhum stream encontrado.",
+ "streams_found": "{{count}} Stream Encontrado",
+ "streams_found_plural": "{{count}} Streams Encontrados",
+ "tap_play_hint": "Toque em Reproduzir para testar um stream no reprodutor nativo.",
+ "unnamed_stream": "Stream Sem Nome",
+ "quality": "Qualidade: {{quality}}",
+ "size": "Tamanho: {{size}}",
+ "url_label": "URL: {{url}}",
+ "headers_info": "Headers: {{count}} cabeçalho(s) personalizado(s)",
+ "find_placeholder": "Procurar no código…",
+ "edit_code_title": "Editar Código",
+ "no_url_stream_error": "Nenhum URL encontrado para este stream"
+ },
+ "repo": {
+ "title": "Testador de Repo",
+ "description": "Obtenha um repositório (URL local ou GitHub raw) e teste cada fornecedor.",
+ "enter_repo_url_error": "Por favor, insira um URL de repositório",
+ "invalid_url_title": "URL Inválido",
+ "invalid_url_msg": "Use um URL raw do GitHub ou um URL local http(s).\n\nExemplo:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main",
+ "manifest_build_error": "Não foi possível criar um URL de manifesto a partir da entrada",
+ "manifest_fetch_error": "Falha ao obter manifesto",
+ "repo_manifest_fetch_error": "Falha ao obter manifesto do repositório",
+ "missing_filename": "Nome de ficheiro em falta no manifesto",
+ "scraper_build_error": "Não foi possível criar um URL de scraper",
+ "download_scraper_error": "Falha ao descarregar scraper",
+ "test_failed": "Teste falhou",
+ "test_parameters": "Parâmetros de Teste de Repo",
+ "test_parameters_desc": "Estes parâmetros são usados apenas para o Testador de Repo.",
+ "using_info": "A usar: {{mediaType}} • TMDB {{tmdbId}}",
+ "using_info_tv": "A usar: {{mediaType}} • TMDB {{tmdbId}} • S{{season}}E{{episode}}",
+ "providers_title": "Fornecedores",
+ "repository_default": "Repositório",
+ "providers_count": "{{count}} fornecedores",
+ "fetch_hint": "Obtenha um repo para listar fornecedores.",
+ "test_all": "Testar Tudo",
+ "status_running": "A EXECUTAR",
+ "status_ok": "OK ({{count}})",
+ "status_ok_empty": "OK (0)",
+ "status_failed": "FALHOU",
+ "status_idle": "INATIVO",
+ "tried_url": "Tentado: {{url}}",
+ "provider_logs": "Logs do Fornecedor",
+ "no_logs_captured": "Nenhum log capturado."
+ }
}
}
\ No newline at end of file
diff --git a/src/screens/PluginTesterScreen.tsx b/src/screens/PluginTesterScreen.tsx
index 9872b2f..90ecc23 100644
--- a/src/screens/PluginTesterScreen.tsx
+++ b/src/screens/PluginTesterScreen.tsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
@@ -10,6 +11,7 @@ import { getPluginTesterStyles, useIsLargeScreen } from './plugin-tester/styles'
const PluginTesterScreen = () => {
const [mainTab, setMainTab] = useState<'individual' | 'repo'>('individual');
+ const { t } = useTranslation();
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
const navigation = useNavigation();
@@ -23,8 +25,8 @@ const PluginTesterScreen = () => {
return (
navigation.goBack()}
/>
diff --git a/src/screens/plugin-tester/IndividualTester.tsx b/src/screens/plugin-tester/IndividualTester.tsx
index f25f558..ad839c1 100644
--- a/src/screens/plugin-tester/IndividualTester.tsx
+++ b/src/screens/plugin-tester/IndividualTester.tsx
@@ -1,4 +1,5 @@
import React, { useState, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
import {
View,
Text,
@@ -8,7 +9,9 @@ import {
ActivityIndicator,
Alert,
KeyboardAvoidingView,
- Platform
+ Platform,
+ Modal,
+ FlatList
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
@@ -26,6 +29,7 @@ interface IndividualTesterProps {
export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
const navigation = useNavigation();
+ const { t } = useTranslation();
const insets = useSafeAreaInsets();
const { currentTheme } = useTheme();
const isLargeScreen = useIsLargeScreen();
@@ -42,6 +46,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
const [streams, setStreams] = useState([]);
const [isRunning, setIsRunning] = useState(false);
const [activeTab, setActiveTab] = useState<'code' | 'logs' | 'results'>('code');
+ const [rightPanelTab, setRightPanelTab] = useState<'logs' | 'results'>('logs');
const [isEditorFocused, setIsEditorFocused] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
@@ -121,7 +126,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
const fetchFromUrl = async () => {
if (!url) {
- Alert.alert('Error', 'Please enter a URL');
+ Alert.alert(t('plugin_tester.common.error'), t('plugin_tester.individual.enter_url_error'));
return;
}
@@ -129,22 +134,26 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
const response = await axios.get(url, { headers: { 'Cache-Control': 'no-cache' } });
const content = typeof response.data === 'string' ? response.data : JSON.stringify(response.data, null, 2);
setCode(content);
- Alert.alert('Success', 'Code loaded from URL');
+ Alert.alert(t('plugin_tester.common.success'), t('plugin_tester.individual.code_loaded'));
} catch (error: any) {
- Alert.alert('Error', `Failed to fetch: ${error.message}`);
+ Alert.alert(t('plugin_tester.common.error'), t('plugin_tester.individual.fetch_error', { message: error.message }));
}
};
const runTest = async () => {
if (!code.trim()) {
- Alert.alert('Error', 'No code to run');
+ Alert.alert(t('plugin_tester.common.error'), t('plugin_tester.individual.no_code_error'));
return;
}
setIsRunning(true);
setLogs([]);
setStreams([]);
- setActiveTab('logs');
+ if (isLargeScreen) {
+ setRightPanelTab('logs');
+ } else {
+ setActiveTab('logs');
+ }
try {
const params = {
@@ -164,7 +173,11 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
setStreams(result.streams);
if (result.streams.length > 0) {
- setActiveTab('results');
+ if (isLargeScreen) {
+ setRightPanelTab('results');
+ } else {
+ setActiveTab('results');
+ }
}
} catch (error: any) {
setLogs(prev => [...prev, `[FATAL] ${error.message}`]);
@@ -183,11 +196,11 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
- Load from URL
+ {t('plugin_tester.individual.load_from_url')}
- Paste a raw GitHub URL or local IP and tap download.
+ {t('plugin_tester.individual.load_from_url_desc')}
{
- Plugin Code
+ {t('plugin_tester.individual.plugin_code')}
setIsEditorFocused(true)}
- accessibilityLabel="Focus code editor"
+ accessibilityLabel={t('plugin_tester.individual.focus_editor')}
>
@@ -227,7 +240,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
value={code}
onChangeText={setCode}
multiline
- placeholder="// Paste plugin code here..."
+ placeholder={t('plugin_tester.individual.code_placeholder')}
placeholderTextColor={currentTheme.colors.mediumEmphasis}
autoCapitalize="none"
autoCorrect={false}
@@ -237,7 +250,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
{/* Test parameters on large screen */}
- Test Parameters
+ {t('plugin_tester.individual.test_parameters')}
@@ -247,20 +260,20 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
onPress={() => setMediaType('movie')}
>
- Movie
+ {t('plugin_tester.common.movie')}
setMediaType('tv')}
>
- TV
+ {t('plugin_tester.common.tv')}
- TMDB ID
+ {t('plugin_tester.common.tmdb_id')}
{
{mediaType === 'tv' && (
<>
- Season
+ {t('plugin_tester.common.season')}
{
/>
- Episode
+ {t('plugin_tester.common.episode')}
{
) : (
)}
- {isRunning ? 'Running…' : 'Run Test'}
+ {isRunning ? t('plugin_tester.common.running') : t('plugin_tester.common.run_test')}
@@ -315,27 +328,22 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
setActiveTab('logs')}
+ onPress={() => setRightPanelTab('logs')}
>
- Logs
+ {t('plugin_tester.tabs.logs')}
setActiveTab('results')}
+ style={[styles.smallTab, rightPanelTab === 'results' && styles.smallTabActive]}
+ onPress={() => setRightPanelTab('results')}
>
- Results ({streams.length})
+ {t('plugin_tester.tabs.results')} ({streams.length})
- {activeTab === 'logs' || activeTab === 'code' ? (
+ {rightPanelTab === 'logs' ? (
(logsScrollRef.current = r)}
style={[styles.logContainer, { flex: 1, minHeight: 400 }]}
@@ -347,7 +355,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
{logs.length === 0 ? (
- No logs yet. Run a test to see output.
+ {t('plugin_tester.individual.no_logs')}
) : (
logs.map((log, i) => {
@@ -366,23 +374,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
)}
) : (
-
- {streams.length === 0 ? (
-
-
- No streams found yet.
-
- ) : (
- streams.map((stream, i) => (
-
- {stream.title || stream.name}
- Quality: {stream.quality || 'Unknown'}
- Size: {stream.description || 'Unknown'}
- URL: {stream.url}
-
- ))
- )}
-
+ renderResultsTab()
)}
@@ -401,11 +393,11 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
- Load from URL
+ {t('plugin_tester.individual.load_from_url')}
- Paste a raw GitHub URL or local IP and tap download.
+ {t('plugin_tester.individual.load_from_url_desc')}
{
- Plugin Code
+ {t('plugin_tester.individual.plugin_code')}
setIsEditorFocused(true)}
- accessibilityLabel="Focus code editor"
+ accessibilityLabel={t('plugin_tester.individual.focus_editor')}
>
@@ -445,7 +437,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
value={code}
onChangeText={setCode}
multiline
- placeholder="// Paste plugin code here..."
+ placeholder={t('plugin_tester.individual.code_placeholder')}
placeholderTextColor={currentTheme.colors.mediumEmphasis}
autoCapitalize="none"
autoCorrect={false}
@@ -456,7 +448,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
- Test Parameters
+ {t('plugin_tester.individual.test_parameters')}
@@ -473,13 +465,13 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
onPress={() => setMediaType('tv')}
>
- TV
+ {t('plugin_tester.common.tv')}
-
- TMDB ID
+
+ {t('plugin_tester.common.tmdb_id')}
{
{mediaType === 'tv' && (
<>
- Season
+ {t('plugin_tester.common.season')}
{
/>
- Episode
+ {t('plugin_tester.common.episode')}
{
) : (
)}
- {isRunning ? 'Running…' : 'Run Test'}
+ {isRunning ? t('plugin_tester.common.running') : t('plugin_tester.common.run_test')}
@@ -542,7 +534,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
{logs.length === 0 ? (
- No logs yet. Run a test to see output.
+ {t('plugin_tester.individual.no_logs')}
) : (
@@ -566,7 +558,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
const playStream = (stream: any) => {
if (!stream.url) {
- Alert.alert('Error', 'No URL found for this stream');
+ Alert.alert(t('plugin_tester.common.error'), t('plugin_tester.individual.no_url_stream_error'));
return;
}
@@ -593,64 +585,63 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
{streams.length === 0 ? (
- No streams found yet.
+ {t('plugin_tester.individual.no_streams')}
) : (
- <>
-
- {streams.length} Stream{streams.length !== 1 ? 's' : ''} Found
- Tap Play to test a stream in the native player.
-
- {streams.map((stream, i) => (
- playStream(stream)}
- activeOpacity={0.7}
- >
-
-
- {stream.title || stream.name || 'Unnamed Stream'}
- Quality: {stream.quality || (stream.title?.match(/(\d+)p/) || [])[1] || 'Unknown'}
- {stream.description && Size: {stream.description}}
- URL: {stream.url}
- {stream.headers && Object.keys(stream.headers).length > 0 && (
-
- Headers: {Object.keys(stream.headers).length} custom header(s)
-
- )}
-
- playStream(stream)}
- >
-
- Play
-
-
-
-
+ {streams.length === 1 ? t('plugin_tester.individual.streams_found', { count: streams.length }) : t('plugin_tester.individual.streams_found_plural', { count: streams.length })}
+ {t('plugin_tester.individual.tap_play_hint')}
+ item.url + index}
+ renderItem={({ item: stream }) => (
+ playStream(stream)}
+ activeOpacity={0.7}
>
- {(() => {
- try {
- return JSON.stringify(stream, null, 2);
- } catch {
- return String(stream);
- }
- })()}
-
-
- ))}
- >
+
+
+ {stream.name || stream.title || t('plugin_tester.individual.unnamed_stream')}
+ {t('plugin_tester.individual.quality', { quality: stream.quality || 'Unknown' })}
+ {stream.description ? {t('plugin_tester.individual.size', { size: stream.description })} : null}
+ {t('plugin_tester.individual.url_label', { url: stream.url })}
+ {stream.headers && Object.keys(stream.headers).length > 0 && (
+ {t('plugin_tester.individual.headers_info', { count: Object.keys(stream.headers).length })}
+ )}
+
+ playStream(stream)}
+ >
+
+ {t('plugin_tester.common.play')}
+
+
+
+
+ {(() => {
+ try {
+ return JSON.stringify(stream, null, 2);
+ } catch {
+ return String(stream);
+ }
+ })()}
+
+
+ )}
+ />
+
)}
);
@@ -662,16 +653,19 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
keyboardVerticalOffset={Platform.OS === 'ios' ? 60 : 0}
>
- jumpToMatch(currentMatchIndex)}
- placeholder="Find in code…"
- placeholderTextColor={currentTheme.colors.mediumEmphasis}
- autoCapitalize="none"
- autoCorrect={false}
- />
+
+
+ jumpToMatch(currentMatchIndex)}
+ placeholder={t('plugin_tester.individual.find_placeholder')}
+ placeholderTextColor={currentTheme.colors.mediumEmphasis}
+ autoCapitalize="none"
+ autoCorrect={false}
+ />
+
{matches.length === 0 ? '–' : `${currentMatchIndex + 1}/${matches.length}`}
@@ -739,7 +733,7 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
scrollEnabled={false}
autoFocus
selectionColor={currentTheme.colors.primary}
- placeholder="// Paste plugin code here..."
+ placeholder={t('plugin_tester.individual.code_placeholder_focused')}
placeholderTextColor={currentTheme.colors.mediumEmphasis}
autoCapitalize="none"
autoCorrect={false}
@@ -752,71 +746,74 @@ export const IndividualTester = ({ onSwitchTab }: IndividualTesterProps) => {
return (
{isEditorFocused ? (
- <>
- setIsEditorFocused(false)}
- rightElement={
- {
- setIsEditorFocused(false);
- setSearchQuery('');
- setMatches([]);
- setCurrentMatchIndex(0);
- }}>
- Done
-
- }
- />
- {renderFocusedEditor()}
- >
+ setIsEditorFocused(false)}
+ >
+
+ setIsEditorFocused(false)}
+ rightElement={
+ setIsEditorFocused(false)}>
+ {t('plugin_tester.common.done')}
+
+ }
+ />
+ {renderFocusedEditor()}
+
+
) : (
<>
navigation.goBack()}
/>
-
- setActiveTab('code')}
- >
- Code
-
- setActiveTab('logs')}
- >
- Logs
-
- setActiveTab('results')}
- >
- Results
-
-
+ {!isLargeScreen && (
+
+ setActiveTab('code')}
+ >
+ {t('plugin_tester.tabs.code')}
+
+ setActiveTab('logs')}
+ >
+ {t('plugin_tester.tabs.logs')}
+
+ setActiveTab('results')}
+ >
+ {t('plugin_tester.tabs.results')}
+
+
+ )}
{activeTab === 'code' && renderCodeTab()}
{activeTab === 'logs' && renderLogsTab()}
{activeTab === 'results' && renderResultsTab()}
>
)}
-
+
);
};
diff --git a/src/screens/plugin-tester/RepoTester.tsx b/src/screens/plugin-tester/RepoTester.tsx
index 24f331e..8017cba 100644
--- a/src/screens/plugin-tester/RepoTester.tsx
+++ b/src/screens/plugin-tester/RepoTester.tsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
import {
View,
Text,
@@ -98,6 +99,7 @@ export const RepoTester = () => {
const { currentTheme } = useTheme();
const isLargeScreen = useIsLargeScreen();
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
+ const { t } = useTranslation();
// Repo tester state
const [repoUrl, setRepoUrl] = useState('');
@@ -120,14 +122,14 @@ export const RepoTester = () => {
const fetchRepository = async () => {
const input = repoUrl.trim();
if (!input) {
- Alert.alert('Error', 'Please enter a repository URL');
+ Alert.alert(t('plugin_tester.common.error'), t('plugin_tester.repo.enter_repo_url_error'));
return;
}
if (!input.startsWith('https://raw.githubusercontent.com/') && !input.startsWith('http://') && !input.startsWith('https://')) {
Alert.alert(
- 'Invalid URL',
- 'Use a GitHub raw URL or a local http(s) URL.\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/main'
+ t('plugin_tester.repo.invalid_url_title'),
+ t('plugin_tester.repo.invalid_url_msg')
);
return;
}
@@ -143,7 +145,7 @@ export const RepoTester = () => {
try {
const candidates = buildManifestCandidates(input);
if (candidates.length === 0) {
- throw new Error('Could not build a manifest URL from the input');
+ throw new Error(t('plugin_tester.repo.manifest_build_error'));
}
let response: any = null;
@@ -168,7 +170,7 @@ export const RepoTester = () => {
}
if (!response) {
- throw lastError || new Error('Failed to fetch manifest');
+ throw lastError || new Error(t('plugin_tester.repo.manifest_fetch_error'));
}
const manifest: RepoManifest = response.data;
@@ -192,10 +194,10 @@ export const RepoTester = () => {
} catch (error: any) {
const status = error?.response?.status;
const statusText = error?.response?.statusText;
- const messageBase = error?.message ? String(error.message) : 'Failed to fetch repository manifest';
+ const messageBase = error?.message ? String(error.message) : t('plugin_tester.repo.repo_manifest_fetch_error');
const message = status ? `${messageBase} (HTTP ${status}${statusText ? ` ${statusText}` : ''})` : messageBase;
setRepoFetchError(message);
- Alert.alert('Error', message);
+ Alert.alert(t('plugin_tester.common.error'), message);
} finally {
setRepoIsFetching(false);
}
@@ -213,7 +215,7 @@ export const RepoTester = () => {
...prev,
[scraper.id]: {
status: 'fail',
- error: 'Missing filename in manifest',
+ error: t('plugin_tester.repo.missing_filename'),
},
}));
return;
@@ -234,7 +236,7 @@ export const RepoTester = () => {
try {
const candidates = buildScraperCandidates(effectiveBase, filename);
- if (candidates.length === 0) throw new Error('Could not build a scraper URL');
+ if (candidates.length === 0) throw new Error(t('plugin_tester.repo.scraper_build_error'));
let res: any = null;
let usedUrl: string | null = null;
@@ -265,7 +267,7 @@ export const RepoTester = () => {
}
if (!res) {
- throw lastError || new Error('Failed to download scraper');
+ throw lastError || new Error(t('plugin_tester.repo.download_scraper_error'));
}
const scraperCode = typeof res.data === 'string' ? res.data : JSON.stringify(res.data, null, 2);
@@ -311,7 +313,7 @@ export const RepoTester = () => {
} catch (error: any) {
const status = error?.response?.status;
const statusText = error?.response?.statusText;
- const messageBase = error?.message ? String(error.message) : 'Test failed';
+ const messageBase = error?.message ? String(error.message) : t('plugin_tester.repo.test_failed');
const message = status ? `${messageBase} (HTTP ${status}${statusText ? ` ${statusText}` : ''})` : messageBase;
setRepoResults(prev => ({
...prev,
@@ -364,11 +366,11 @@ export const RepoTester = () => {
- Repo Tester
+ {t('plugin_tester.repo.title')}
- Fetch a repository (local URL or GitHub raw) and test each provider.
+ {t('plugin_tester.repo.description')}
@@ -402,17 +404,17 @@ export const RepoTester = () => {
{!!repoFetchTriedUrl && (
- Trying: {repoFetchTriedUrl}
+ {t('plugin_tester.repo.tried_url', { url: repoFetchTriedUrl })}
)}
- Repo Test Parameters
+ {t('plugin_tester.repo.test_parameters')}
- These parameters are used only for Repo Tester.
+ {t('plugin_tester.repo.test_parameters_desc')}
{
onPress={() => setRepoMediaType('movie')}
>
- Movie
+ {t('plugin_tester.common.movie')}
setRepoMediaType('tv')}
>
- TV
+ {t('plugin_tester.common.tv')}
- TMDB ID
+ {t('plugin_tester.common.tmdb_id')}
{
{repoMediaType === 'tv' && (
<>
- Season
+ {t('plugin_tester.common.season')}
{
/>
- Episode
+ {t('plugin_tester.common.episode')}
{
- Using: {repoMediaType.toUpperCase()} • TMDB {repoTmdbId}{repoMediaType === 'tv' ? ` • S${repoSeason}E${repoEpisode}` : ''}
+ {repoMediaType === 'tv'
+ ? t('plugin_tester.repo.using_info_tv', { mediaType: repoMediaType.toUpperCase(), tmdbId: repoTmdbId, season: repoSeason, episode: repoEpisode })
+ : t('plugin_tester.repo.using_info', { mediaType: repoMediaType.toUpperCase(), tmdbId: repoTmdbId })}
- Providers
+ {t('plugin_tester.repo.providers_title')}
{repoManifest ? (
- {repoManifest.name || 'Repository'} • {repoScrapers.length} providers
+ {repoManifest.name || t('plugin_tester.repo.repository_default')} • {t('plugin_tester.repo.providers_count', { count: repoScrapers.length })}
) : (
- Fetch a repo to list providers.
+ {t('plugin_tester.repo.fetch_hint')}
)}
{repoScrapers.length > 0 && (
@@ -496,7 +500,7 @@ export const RepoTester = () => {
) : (
)}
- {repoIsTestingAll ? 'Testing…' : 'Test All'}
+ {repoIsTestingAll ? t('plugin_tester.common.testing') : t('plugin_tester.repo.test_all')}
{
const getStatusText = () => {
switch (result.status) {
case 'running':
- return 'RUNNING';
+ return t('plugin_tester.repo.status_running');
case 'ok':
- return `OK (${result.streamsCount ?? 0})`;
+ return t('plugin_tester.repo.status_ok', { count: result.streamsCount ?? 0 });
case 'ok-empty':
- return 'OK (0)';
+ return t('plugin_tester.repo.status_ok_empty');
case 'fail':
- return 'FAILED';
+ return t('plugin_tester.repo.status_failed');
default:
- return 'IDLE';
+ return t('plugin_tester.repo.status_idle');
}
};
@@ -573,7 +577,7 @@ export const RepoTester = () => {
{!!result.triedUrl && result.status === 'fail' && (
- Tried: {result.triedUrl}
+ {t('plugin_tester.repo.tried_url', { url: result.triedUrl })}
)}
{!!result.error && (
@@ -584,10 +588,10 @@ export const RepoTester = () => {
{repoOpenLogsForId === scraper.id && (
- Provider Logs
+ {t('plugin_tester.repo.provider_logs')}
- {(result.logs && result.logs.length > 0) ? result.logs.join('\n') : 'No logs captured.'}
+ {(result.logs && result.logs.length > 0) ? result.logs.join('\n') : t('plugin_tester.repo.no_logs_captured')}
@@ -604,14 +608,14 @@ export const RepoTester = () => {
onPress={() => testRepoScraper(scraper)}
disabled={result.status === 'running' || repoIsTestingAll}
>
- Test
+ {t('plugin_tester.common.test')}
setRepoOpenLogsForId(prev => (prev === scraper.id ? null : scraper.id))}
disabled={result.status === 'idle' || result.status === 'running'}
>
- Logs
+ {t('plugin_tester.tabs.logs')}
diff --git a/src/screens/plugin-tester/components.tsx b/src/screens/plugin-tester/components.tsx
index 64a3f57..3e03908 100644
--- a/src/screens/plugin-tester/components.tsx
+++ b/src/screens/plugin-tester/components.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { useTranslation } from 'react-i18next';
import { View, Text, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useTheme } from '../../contexts/ThemeContext';
@@ -39,6 +40,7 @@ interface MainTabBarProps {
}
export const MainTabBar = ({ activeTab, onTabChange }: MainTabBarProps) => {
+ const { t } = useTranslation();
const { currentTheme } = useTheme();
const isLargeScreen = useIsLargeScreen();
const styles = getPluginTesterStyles(currentTheme, isLargeScreen);
@@ -50,14 +52,14 @@ export const MainTabBar = ({ activeTab, onTabChange }: MainTabBarProps) => {
onPress={() => onTabChange('individual')}
>
- Individual
+ {t('plugin_tester.tabs.individual')}
onTabChange('repo')}
>
- Repo Tester
+ {t('plugin_tester.tabs.repo')}
);
diff --git a/src/screens/plugin-tester/styles.ts b/src/screens/plugin-tester/styles.ts
index 53382ed..e026aab 100644
--- a/src/screens/plugin-tester/styles.ts
+++ b/src/screens/plugin-tester/styles.ts
@@ -56,7 +56,7 @@ export const getPluginTesterStyles = (theme: any, isLargeScreen: boolean = false
backgroundColor: theme.colors.elevation1,
padding: 6,
marginHorizontal: 16,
- marginTop: 12,
+ marginVertical: 12,
borderRadius: 12,
borderWidth: 1,
borderColor: theme.colors.elevation3,
@@ -479,4 +479,116 @@ export const getPluginTesterStyles = (theme: any, isLargeScreen: boolean = false
color: theme.colors.mediumGray,
marginTop: 8,
},
+ // New styles added for i18n
+ smallTab: {
+ flex: 1,
+ paddingVertical: 8,
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: theme.colors.elevation3,
+ backgroundColor: theme.colors.elevation1,
+ },
+ smallTabActive: {
+ backgroundColor: theme.colors.primary + '20',
+ borderColor: theme.colors.primary,
+ },
+ smallTabText: {
+ fontSize: 12,
+ fontWeight: '600',
+ color: theme.colors.mediumEmphasis,
+ },
+ smallTabTextActive: {
+ color: theme.colors.primary,
+ },
+ listContainer: {
+ flex: 1,
+ },
+ sectionHeader: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: theme.colors.highEmphasis,
+ marginBottom: 4,
+ },
+ sectionSubHeader: {
+ fontSize: 12,
+ color: theme.colors.mediumEmphasis,
+ marginBottom: 10,
+ },
+ streamInfo: {
+ flex: 1,
+ marginRight: 10,
+ },
+ streamName: {
+ fontSize: 14,
+ fontWeight: '700',
+ color: theme.colors.white,
+ marginBottom: 2,
+ },
+ streamMeta: {
+ fontSize: 12,
+ color: theme.colors.mediumEmphasis,
+ marginTop: 2,
+ },
+ playButton: {
+ backgroundColor: theme.colors.primary,
+ borderRadius: 8,
+ paddingVertical: 8,
+ paddingHorizontal: 12,
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 6,
+ },
+ playButtonText: {
+ fontSize: 12,
+ fontWeight: '700',
+ color: theme.colors.white,
+ },
+ searchContainer: {
+ flex: 1,
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: theme.colors.elevation1,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: theme.colors.elevation3,
+ paddingHorizontal: 10,
+ },
+ searchIcon: {
+ marginRight: 8,
+ },
+ searchInput: {
+ flex: 1,
+ paddingVertical: 8,
+ color: theme.colors.highEmphasis,
+ fontSize: 14,
+ },
+ modalContainer: {
+ flex: 1,
+ },
+ mobileTabBar: {
+ flexDirection: 'row',
+ backgroundColor: theme.colors.elevation2,
+ borderTopWidth: 1,
+ borderTopColor: theme.colors.elevation3,
+ paddingTop: 10,
+ },
+ mobileTabItem: {
+ flex: 1,
+ alignItems: 'center',
+ paddingVertical: 6,
+ gap: 4,
+ },
+ mobileTabItemActive: {
+ // Active styles
+ },
+ mobileTabText: {
+ fontSize: 11,
+ fontWeight: '600',
+ color: theme.colors.mediumEmphasis,
+ },
+ mobileTabTextActive: {
+ color: theme.colors.primary,
+ },
});