From 4b212c08e27137058153bdff5b0286fc36f7392f Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 30 Sep 2025 22:10:36 +0300 Subject: [PATCH] Add French localization and update language support Added a complete French translation file (fr.json) and updated all other language files to include 'French' as a selectable language. Updated format table color logic to recognize French and German quality terms for better internationalization. Also updated the default English language map to include French. --- languages/de.json | 1 + languages/en.json | 1 + languages/es.json | 3 +- languages/fr.json | 331 +++++++++++++++++++++++++++++ languages/pt.json | 1 + languages/ru.json | 1 + languages/zh.json | 1 + src/gui/ytsage_gui_format_table.py | 11 +- src/utils/ytsage_localization.py | 3 +- 9 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 languages/fr.json diff --git a/languages/de.json b/languages/de.json index 17fd0ee..a707cb1 100644 --- a/languages/de.json +++ b/languages/de.json @@ -10,6 +10,7 @@ "russian": "Russisch", "chinese": "Chinesisch", "german": "Deutsch", + "french": "Französisch", "help_text": "Wählen Sie Ihre bevorzugte Sprache für die Benutzeroberfläche.", "restart_notice": "Die Sprachänderung wird nach dem Neustart der Anwendung wirksam." }, diff --git a/languages/en.json b/languages/en.json index a852430..e1b598f 100644 --- a/languages/en.json +++ b/languages/en.json @@ -10,6 +10,7 @@ "russian": "Русский (Russian)", "chinese": "中文 (简体) (Chinese Simplified)", "german": "Deutsch (German)", + "french": "Français (French)", "help_text": "Select your preferred language for the interface.", "restart_notice": "Language change will take effect after restarting the application." }, diff --git a/languages/es.json b/languages/es.json index 4339bad..c3519f0 100644 --- a/languages/es.json +++ b/languages/es.json @@ -9,7 +9,8 @@ "portuguese": "Português (Portuguese)", "russian": "Русский (Russian)", "chinese": "中文 (简体) (Chinese Simplified)", - "german": "Deutsch (German)" + "german": "Deutsch (German)", + "french": "Français (French)" }, "app": { "title": "YTSage", diff --git a/languages/fr.json b/languages/fr.json new file mode 100644 index 0000000..439e03a --- /dev/null +++ b/languages/fr.json @@ -0,0 +1,331 @@ +{ + "language": { + "display_name": "Français (French)", + "select_language": "Sélectionner la langue :", + "current_language": "Langue actuelle : {language}", + "restart_required": "Les modifications de langue prendront effet après le redémarrage de l'application.", + "english": "Anglais", + "spanish": "Espagnol", + "portuguese": "Portugais", + "russian": "Russe", + "chinese": "Chinois", + "german": "Allemand", + "french": "Français", + "help_text": "Sélectionnez votre langue préférée pour l'interface.", + "restart_notice": "Le changement de langue prendra effet après le redémarrage de l'application." + }, + "app": { + "title": "YTSage", + "version": "v{version}", + "ready": "Prêt" + }, + "formats": { + "show_formats": "Afficher les formats :", + "video_format": "Format vidéo :", + "audio_format": "Format audio :", + "no_formats": "Aucun format disponible", + "loading": "Chargement des formats...", + "select": "Sélectionner", + "quality": "Qualité", + "extension": "Extension", + "resolution": "Résolution", + "file_size": "Taille du fichier", + "codec": "Codec", + "audio": "Audio", + "notes": "Notes", + "will_merge_audio": "L'audio sera fusionné", + "has_audio": "✓ Contient de l'audio", + "audio_only": "Audio uniquement", + "large_size": "Taille importante", + "medium_size": "Taille moyenne", + "standard_size": "Taille standard", + "compatible": "Compatible", + "efficient": "Efficace", + "high_quality": "Haute qualité", + "best_4k": "Meilleure (4K)", + "best_2k": "Meilleure (2K)", + "high_1080p": "Haute (1080p)", + "high_720p": "Haute (720p)", + "medium_480p": "Moyenne (480p)", + "low_quality": "Qualité faible", + "best_audio": "Meilleur audio", + "high_audio": "Audio élevé", + "medium_audio": "Audio moyen", + "low_audio": "Audio faible", + "audio_only_resolution": "Audio uniquement" + }, + "buttons": { + "download": "Télécharger", + "pause": "Pause", + "resume": "Reprendre", + "cancel": "Annuler", + "browse": "Parcourir", + "clear": "Effacer", + "ok": "OK", + "apply": "Appliquer", + "close": "Fermer", + "run_command": "Exécuter la commande", + "custom_command_help": "Aide", + "about": "À propos", + "analyze": "Analyser", + "paste_url": "Coller l'URL", + "select_videos": "Sélectionner les vidéos...", + "video": "Vidéo", + "audio_only": "Audio uniquement", + "custom_options": "Options personnalisées", + "trim_video": "Découper la vidéo", + "download_settings": "Paramètres de téléchargement", + "update": "Mettre à jour yt-dlp", + "select_defaults": "Sélectionner par défaut", + "select_all": "Tout sélectionner", + "deselect_all": "Tout désélectionner" + }, + "dialogs": { + "custom_options": "Options personnalisées", + "settings": "Paramètres", + "select_folder": "Sélectionner le dossier de téléchargement", + "sponsorblock_categories": "Catégories SponsorBlock", + "sponsorblock_description": "Choisissez les types de segments vidéo à supprimer automatiquement pendant le téléchargement.\nSponsorBlock utilise des données soumises par la communauté pour identifier ces segments.", + "select_subtitles": "Sélectionner les sous-titres", + "filter_languages_placeholder": "Filtrer les langues (ex: en, fr)...", + "no_subtitles_available": "Aucun sous-titre disponible", + "matching": "correspondant" + }, + "tabs": { + "cookies": "Se connecter avec des cookies", + "custom_command": "Commande personnalisée", + "proxy": "Proxy", + "language": "Langue" + }, + "cookies": { + "help_text": "Choisissez comment fournir les cookies pour l'authentification.\nCeci permet de télécharger des vidéos privées et des fichiers audio de haute qualité.", + "cookie_source": "Source des cookies", + "use_cookie_file": "Utiliser un fichier de cookies", + "extract_from_browser": "Extraire du navigateur", + "cookie_file": "Fichier de cookies", + "cookie_file_placeholder": "Chemin vers le fichier cookies.txt...", + "browser_selection": "Sélection du navigateur", + "browser_help": "Sélectionnez le navigateur depuis lequel extraire les cookies :", + "browser_label": "Navigateur :", + "profile_label": "Profil :", + "profile_placeholder": "Par défaut", + "browser_extract_message": "Les cookies du navigateur seront extraits lors de l'application", + "file_selected_message": "Fichier de cookies sélectionné - Cliquez sur OK pour appliquer", + "select_file_title": "Sélectionner le fichier de cookies", + "file_filter": "Fichiers de cookies (*.txt *.lwp)" + }, + "custom_command": { + "help_text": "Entrez votre commande yt-dlp personnalisée ci-dessous. L'URL actuelle sera automatiquement ajoutée.

Pour la liste complète des options et exemples d'utilisation cliquez ici pour voir la documentation officielle yt-dlp.

Note : Le chemin de téléchargement et le modèle de nom de fichier sont gérés automatiquement.", + "input_label": "Arguments yt-dlp :", + "input_placeholder": "Entrez les arguments yt-dlp ici...\n\nEx : --extract-audio --audio-format mp3", + "output_label": "Sortie de la commande :", + "output_placeholder": "La sortie de la commande apparaîtra ici...", + "command_placeholder": "Entrer une commande yt-dlp personnalisée...", + "command_help": "Espaces réservés disponibles :\n{url} - URL de la vidéo\n{output} - Dossier de sortie\n\nExemple : --write-info-json --write-thumbnail", + "full_command": "🔧 Commande complète : {command}", + "command_success": "✅ Commande personnalisée exécutée avec succès !", + "command_failed": "❌ Commande échouée avec le code de sortie {code}", + "command_error": "❌ Erreur lors de l'exécution de la commande personnalisée : {error}" + }, + "proxy": { + "help_text": "Configurer les paramètres proxy pour les téléchargements. Laisser vide pour une connexion directe.", + "main_proxy": "Proxy principal", + "main_proxy_help": "Serveur proxy principal pour tous les téléchargements. Prend en charge les protocoles HTTP/HTTPS et SOCKS5.", + "proxy_url_label": "URL du proxy :", + "proxy_url_placeholder": "http://proxy:port ou socks5://proxy:port", + "proxy_examples": "Exemples :\n• HTTP : http://proxy.example.com:8080\n• SOCKS5 : socks5://proxy.example.com:1080", + "geo_proxy": "Proxy de contournement géographique", + "geo_proxy_help": "Proxy secondaire spécialement pour contourner les restrictions géographiques.", + "geo_proxy_url_label": "URL du proxy de contournement géographique :", + "geo_proxy_url_placeholder": "http://geo-proxy:port", + "clear_main_proxy": "Effacer le proxy principal", + "clear_geo_proxy": "Effacer le proxy géographique", + "proxy_url": "URL du proxy", + "proxy_placeholder": "http://proxy:port ou socks5://proxy:port", + "geo_bypass": "Proxy de contournement géographique (pour les restrictions géographiques)", + "geo_bypass_placeholder": "http://proxy:port pour contourner le géo-blocage", + "invalid_main_url": "Format d'URL de proxy principal invalide", + "invalid_geo_url": "Format d'URL de proxy géographique invalide", + "main_configured": "Proxy principal configuré", + "geo_configured": "Proxy géographique configuré" + }, + "download": { + "preparing": "Préparation du téléchargement...", + "please_enter_url": "Veuillez d'abord entrer une URL.", + "please_set_path": "Veuillez d'abord définir un chemin de téléchargement.", + "please_enter_url_and_path": "Veuillez entrer une URL et définir un chemin de téléchargement.", + "please_select_format": "Veuillez d'abord sélectionner un format." + }, + "update": { + "title": "Mettre à jour yt-dlp", + "checking": "Vérification des mises à jour...", + "update_available": "Mise à jour disponible !\nVersion actuelle : {current}\nDernière version : {latest}", + "up_to_date": "yt-dlp est à jour (Version {version})", + "could_not_determine": "Impossible de déterminer les versions.", + "error_comparing": "Erreur lors de la comparaison des versions : {error}", + "update_available_failed": "Mise à jour disponible ! (Comparaison échouée)\nActuelle : {current}\nDernière : {latest}", + "updating": "Mise à jour...", + "initializing": "🚀 Initialisation du processus de mise à jour...", + "checking_current": "🔍 Vérification de l'installation actuelle...", + "found_at": "📍 yt-dlp trouvé à : {path}", + "error_getting_path": "❌ Erreur lors de la récupération du chemin yt-dlp : {error}", + "updating_binary": "📦 Mise à jour du binaire yt-dlp géré par l'application...", + "updating_pip": "🐍 Mise à jour du yt-dlp système via pip...", + "update_failed": "❌ Échec de la mise à jour yt-dlp. Veuillez réessayer ou vérifier votre connexion Internet.", + "binary_updated": "✅ Binaire mis à jour avec succès !", + "update_failed_stderr": "❌ Échec de la mise à jour yt-dlp : {error}", + "update_timeout": "❌ Délai d'attente de la mise à jour yt-dlp dépassé.", + "unexpected_error": "❌ Erreur inattendue lors de la mise à jour : {error}", + "checking_pip": "🔍 Vérification de l'installation pip actuelle...", + "current_version": "📋 Version actuelle : {version}", + "not_found_pip": "⚠️ yt-dlp non trouvé via pip, tentative d'installation...", + "checking_latest": "🌐 Vérification de la dernière version...", + "failed_check_updates": "❌ Échec de la vérification des mises à jour", + "latest_version": "🆕 Dernière version : {version}", + "updating_from_to": "⬆️ Mise à jour de {current} vers {latest}...", + "running_pip_install": "📦 Exécution de pip install --upgrade...", + "pip_completed": "✅ Mise à jour pip terminée avec succès !", + "pip_failed": "❌ Échec de la mise à jour pip : {error}" + }, + "about": { + "title": "À propos de YTSage", + "version": "Version {version}", + "description": "Téléchargeur YouTube moderne avec une interface PySide6 propre.", + "author": "Par : {author}", + "github": "GitHub : {repo}", + "system_info": "Informations système", + "loading": "🔄 Chargement des informations système...", + "refresh": "🔄", + "refreshing": "🔄 Actualisation...", + "refresh_failed": "Échec de l'actualisation", + "refresh_failed_message": "Impossible d'actualiser les informations de version.", + "detected": "✓ Détecté", + "missing": "✗ Manquant", + "not_available": "Non disponible" + }, + "time_range": { + "title": "Découper la vidéo", + "time_range_group": "Plage horaire", + "start_time": "Heure de début (HH:MM:SS)", + "end_time": "Heure de fin (HH:MM:SS)", + "start_placeholder": "00:00:00", + "end_placeholder": "00:00:00", + "start_time_placeholder": "Heure de début (HH:MM:SS)", + "end_time_placeholder": "Heure de fin (HH:MM:SS)", + "force_keyframes": "Forcer les images clés aux points de coupe", + "help_text": "Définissez les heures de début et de fin pour découper la vidéo.\nLaissez vide pour télécharger la vidéo complète.", + "invalid_format": "Format d'heure invalide. Utilisez le format HH:MM:SS.", + "start_after_end": "L'heure de début ne peut pas être après l'heure de fin." + }, + "settings": { + "title": "Paramètres de téléchargement", + "download_path": "Chemin de téléchargement", + "browse": "Parcourir...", + "speed_limit": "Limite de vitesse", + "speed_limit_placeholder": "Aucune", + "auto_update_ytdlp": "Mise à jour automatique de yt-dlp", + "enable_auto_updates": "Activer les mises à jour automatiques de yt-dlp", + "update_frequency": "Fréquence de mise à jour :", + "check_startup": "Vérifier à chaque démarrage (minimum 1 heure entre les vérifications)", + "check_daily": "Vérifier quotidiennement", + "check_weekly": "Vérifier hebdomadairement", + "check_updates_now": "Vérifier les mises à jour maintenant", + "update_check_title": "Vérification de mise à jour", + "could_not_determine_version": "Impossible de déterminer la version actuelle de yt-dlp.", + "update_available_dialog": "Mise à jour disponible !\n\nActuelle : {current}\nDernière : {latest}\n\nUtilisez le bouton 'Mettre à jour yt-dlp' dans la fenêtre principale pour mettre à jour.", + "up_to_date_dialog": "yt-dlp est à jour !\n\nVersion actuelle : {version}", + "error_checking_updates": "Erreur lors de la vérification des mises à jour : {error}", + "settings_saved_title": "Paramètres sauvegardés", + "settings_saved_message": "Les paramètres de mise à jour automatique ont été sauvegardés avec succès !", + "error_title": "Erreur", + "failed_save_settings": "Échec de la sauvegarde des paramètres de mise à jour automatique.", + "error_saving_settings": "Erreur lors de la sauvegarde des paramètres de mise à jour automatique : {error}", + "auto_update_title": "Paramètres de mise à jour automatique", + "auto_update_header": "🔄 Paramètres de mise à jour automatique", + "auto_update_description": "Configurez les mises à jour automatiques pour yt-dlp afin de vous assurer d'avoir toujours les dernières fonctionnalités et corrections de bogues.", + "current_status": "État actuel", + "current_version_label": "Version actuelle de yt-dlp : Vérification...", + "last_check_label": "Dernière vérification de mise à jour : Jamais", + "next_check_label": "Prochaine vérification : Basée sur les paramètres", + "manual_check_button": "🔍 Vérifier les mises à jour maintenant", + "update_frequency_group": "Fréquence de mise à jour", + "save_settings": "Sauvegarder les paramètres", + "settings_saved_successfully": "✅ Paramètres sauvegardés avec succès !", + "error_saving": "❌ Erreur lors de la sauvegarde des paramètres : {error}" + }, + "main_ui": { + "url_placeholder": "Entrer l'URL de la vidéo ou de la playlist YouTube", + "merge_subtitles": "Fusionner les sous-titres", + "save_thumbnail": "Sauvegarder la miniature", + "save_description": "Sauvegarder la description", + "embed_chapters": "Intégrer les chapitres", + "subtitles_selected": "{count} sélectionné(s)", + "all_selected": "Tous sélectionnés", + "select_videos_all": "Sélectionner les vidéos... (Tous sélectionnés)", + "please_enter_url": "Veuillez d'abord entrer une URL", + "cookie_file_selected_title": "Fichier de cookies sélectionné", + "cookie_file_selected_message": "Fichier de cookies sélectionné : {path}", + "browser_cookies_selected_title": "Cookies de navigateur sélectionnés", + "browser_cookies_selected_message": "Les cookies du navigateur seront extraits depuis : {browser}", + "error_no_format_info": "Erreur : Aucune information de format disponible.", + "error_extract_info": "Erreur : Impossible d'extraire les informations de base de la vidéo. Veuillez vérifier votre lien.", + "analyzing_preparing": "Analyse (0%)... Préparation de la requête", + "analyzing_extracting_basic": "Analyse (15%)... Extraction des informations de base", + "analyzing_extracting_detailed": "Analyse (30%)... Extraction des informations détaillées", + "analyzing_processing_video": "Analyse (45%)... Traitement des données vidéo", + "analyzing_processing_formats": "Analyse (60%)... Traitement des formats", + "analyzing_loading_thumbnail": "Analyse (75%)... Chargement de la miniature", + "analyzing_processing_subtitles": "Analyse (85%)... Traitement des sous-titres", + "analyzing_updating_table": "Analyse (95%)... Mise à jour du tableau des formats", + "analysis_complete": "Analyse terminée !", + "analyzing_extracting_ytdlp": "Analyse (30%)... Extraction d'informations avec l'exécutable yt-dlp", + "analyzing_processing_data": "Analyse (60%)... Traitement des données", + "analyzing_processing_formats_ytdlp": "Analyse (75%)... Traitement des formats", + "analyzing_loading_thumbnail_ytdlp": "Analyse (85%)... Chargement de la miniature", + "analyzing_processing_subtitles_ytdlp": "Analyse (90%)... Traitement des sous-titres", + "select_subtitles": "Sélectionner les sous-titres...", + "sponsorblock_categories": "Catégories SponsorBlock..." + }, + "sponsorblock": { + "sponsor": "Sponsor", + "sponsor_desc": "Publicité payante, recommandations payantes et publicité directe", + "selfpromo": "Auto-promotion non payante", + "selfpromo_desc": "Publicité non payante pour le contenu propre du créateur", + "interaction": "Rappel d'interaction", + "interaction_desc": "Les spectateurs sont invités à aimer, s'abonner ou suivre sur les réseaux sociaux", + "intro": "Intro", + "intro_desc": "Introduction de la vidéo qui peut être ignorée", + "outro": "Outro/Cartes de fin", + "outro_desc": "Crédits ou fin de la vidéo", + "preview": "Aperçu/Récapitulatif", + "preview_desc": "Bref récapitulatif des vidéos précédentes ou aperçu du contenu à venir", + "music_offtopic": "Section hors-musique", + "music_offtopic_desc": "Uniquement pour les vidéos musicales. Marque les sections non-musicales", + "filler": "Digression de remplissage", + "filler_desc": "Scènes de diversion ajoutées uniquement comme remplissage ou pour l'humour" + }, + "video_info": { + "channel": "Chaîne", + "views": "Vues", + "likes": "J'aime", + "upload_date": "Date de mise en ligne", + "duration": "Durée", + "unknown_channel": "Chaîne inconnue", + "unknown_date": "Date inconnue", + "unknown_title": "Titre inconnu" + }, + "command": { + "running": "En cours...", + "run_command": "Exécuter la commande" + }, + "selection": { + "none_selected": "0 sélectionné", + "one_selected": "1 catégorie sélectionnée", + "count_selected": "{count} sélectionné(s)" + }, + "status": { + "ready": "Prêt", + "file_exists": "⚠️ Le fichier existe déjà", + "cancelling": "Annulation du téléchargement..." + } +} \ No newline at end of file diff --git a/languages/pt.json b/languages/pt.json index 29f9727..6bc935e 100644 --- a/languages/pt.json +++ b/languages/pt.json @@ -10,6 +10,7 @@ "russian": "Русский (Russian)", "chinese": "中文 (简体) (Chinese Simplified)", "german": "Alemão", + "french": "Francês", "help_text": "Selecione seu idioma preferido para a interface.", "restart_notice": "A alteração de idioma terá efeito após reiniciar a aplicação." }, diff --git a/languages/ru.json b/languages/ru.json index e48501d..b4db0ca 100644 --- a/languages/ru.json +++ b/languages/ru.json @@ -10,6 +10,7 @@ "russian": "Русский", "chinese": "中文 (简体) (Chinese Simplified)", "german": "Немецкий", + "french": "Французский", "help_text": "Выберите предпочитаемый язык для интерфейса.", "restart_notice": "Изменение языка вступит в силу после перезапуска приложения." }, diff --git a/languages/zh.json b/languages/zh.json index 96ea952..b89adc3 100644 --- a/languages/zh.json +++ b/languages/zh.json @@ -10,6 +10,7 @@ "russian": "俄语", "chinese": "中文", "german": "德语", + "french": "法语", "help_text": "选择您喜欢的界面语言。", "restart_notice": "语言更改将在重启应用程序后生效。" }, diff --git a/src/gui/ytsage_gui_format_table.py b/src/gui/ytsage_gui_format_table.py index e3180c0..f04f86b 100644 --- a/src/gui/ytsage_gui_format_table.py +++ b/src/gui/ytsage_gui_format_table.py @@ -264,14 +264,15 @@ class FormatTableMixin: # Column 1: Quality (Always shown) quality_text = self.get_quality_label(f) quality_item = QTableWidgetItem(quality_text) - # Set color based on quality (check English, Spanish, Portuguese, Russian, and Chinese terms) - if any(term in quality_text for term in ["Best", "Óptima", "Mejor", "Melhor", "Лучшее", "最佳", "Beste"]): + # Set color based on quality (check English, Spanish, Portuguese, Russian, Chinese, German, and French terms) + quality_lower = quality_text.lower() # Make comparison case-insensitive + if any(term.lower() in quality_lower for term in ["Best", "Óptima", "Mejor", "Melhor", "Лучшее", "最佳", "Beste", "Meilleure"]): quality_item.setForeground(QColor("#00ff00")) # Green for best quality - elif any(term in quality_text for term in ["High", "Alta", "Alto", "Áudio Alto", "Audio Alto", "Высокое", "高清", "高质量", "Hoch"]): + elif any(term.lower() in quality_lower for term in ["High", "Alta", "Alto", "Áudio Alto", "Audio Alto", "Высокое", "高清", "高质量", "Hoch", "Haute", "Élevé", "Audio élevé"]): quality_item.setForeground(QColor("#00cc00")) # Light green for high quality - elif any(term in quality_text for term in ["Medium", "Media", "Medio", "Média", "Áudio Médio", "Audio Medio", "Среднее", "中等", "Mittel"]): + elif any(term.lower() in quality_lower for term in ["Medium", "Media", "Medio", "Média", "Áudio Médio", "Audio Medio", "Среднее", "中等", "Mittel", "Moyenne", "Audio moyen"]): quality_item.setForeground(QColor("#ffaa00")) # Orange for medium quality - elif any(term in quality_text for term in ["Low", "Baja", "Bajo", "Baixa", "Áudio Baixo", "Audio Bajo", "Низкое", "低质量", "Niedrig", "Niedriges Audio"]): + elif any(term.lower() in quality_lower for term in ["Low", "Baja", "Bajo", "Baixa", "Áudio Baixo", "Audio Bajo", "Низкое", "低质量", "Niedrig", "Niedriges Audio", "Faible", "Audio faible", "Qualité faible"]): quality_item.setForeground(QColor("#ff5555")) # Red for low quality self.format_table.setItem(row, 1, quality_item) diff --git a/src/utils/ytsage_localization.py b/src/utils/ytsage_localization.py index a31c4c8..5826694 100644 --- a/src/utils/ytsage_localization.py +++ b/src/utils/ytsage_localization.py @@ -85,7 +85,8 @@ class LocalizationManager: "portuguese": "Português (Portuguese)", "russian": "Русский (Russian)", "chinese": "中文 (简体) (Chinese Simplified)", - "german": "Deutsch (German)" + "german": "Deutsch (German)", + "french": "Français (French)" }, "download": { "preparing": "🚀 Preparing your download...",