0ccb974708
Added a new Arabic translation file (ar.json) and updated all other language files to include 'arabic' in the language selection list. Also updated quality label color detection in the format table to support Arabic terms, ensuring proper highlighting for quality levels in Arabic.
277 lines
9.2 KiB
Python
277 lines
9.2 KiB
Python
"""
|
|
Localization Manager Module
|
|
==========================
|
|
|
|
This module provides centralized localization support for YTSage application.
|
|
It handles loading language files, switching languages, and retrieving localized strings.
|
|
|
|
Features
|
|
--------
|
|
- Thread-safe operations for getting localized text
|
|
- Fallback to English when translation is missing
|
|
- Support for multiple languages via JSON files
|
|
- Dynamic language switching without restart
|
|
- Nested key support with dot notation
|
|
|
|
Usage
|
|
-----
|
|
from src.utils.ytsage_localization import LocalizationManager
|
|
|
|
# Get localized text
|
|
text = LocalizationManager.get_text("download.ready")
|
|
button_text = LocalizationManager.get_text("buttons.download")
|
|
|
|
# Change language
|
|
LocalizationManager.set_language("es")
|
|
|
|
# Get available languages
|
|
languages = LocalizationManager.get_available_languages()
|
|
"""
|
|
|
|
import json
|
|
import threading
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
from src.utils.ytsage_logger import logger
|
|
|
|
|
|
class LocalizationManager:
|
|
"""
|
|
Thread-safe localization manager for YTSage.
|
|
|
|
Handles loading, caching, and retrieving localized strings from JSON language files.
|
|
"""
|
|
|
|
_lock = threading.RLock()
|
|
_current_language = "en"
|
|
_languages: Dict[str, Dict[str, Any]] = {}
|
|
_languages_dir = Path(__file__).parent.parent.parent / "languages"
|
|
|
|
# Fallback English strings embedded in code
|
|
_fallback_strings = {
|
|
"app": {
|
|
"title": "YTSage",
|
|
"version": "v{version}",
|
|
"ready": "Ready"
|
|
},
|
|
"buttons": {
|
|
"download": "Download",
|
|
"pause": "Pause",
|
|
"resume": "Resume",
|
|
"cancel": "Cancel",
|
|
"browse": "Browse",
|
|
"clear": "Clear",
|
|
"ok": "OK",
|
|
"apply": "Apply",
|
|
"close": "Close"
|
|
},
|
|
"dialogs": {
|
|
"custom_options": "Custom Options",
|
|
"settings": "Settings"
|
|
},
|
|
"tabs": {
|
|
"cookies": "Login with Cookies",
|
|
"custom_command": "Custom Command",
|
|
"proxy": "Proxy",
|
|
"language": "Language"
|
|
},
|
|
"language": {
|
|
"select_language": "Select Language:",
|
|
"current_language": "Current language: {language}",
|
|
"restart_required": "Language changes will take effect after restarting the application.",
|
|
"english": "English",
|
|
"spanish": "Español (Spanish)",
|
|
"portuguese": "Português (Portuguese)",
|
|
"russian": "Русский (Russian)",
|
|
"chinese": "中文 (简体) (Chinese Simplified)",
|
|
"german": "Deutsch (German)",
|
|
"french": "Français (French)",
|
|
"hindi": "हिन्दी (Hindi)",
|
|
"indonesian": "Bahasa Indonesia (Indonesian)",
|
|
"turkish": "Türkçe (Turkish)",
|
|
"polish": "Polski (Polish)",
|
|
"italian": "Italiano (Italian)",
|
|
"arabic": "العربية (Arabic)"
|
|
},
|
|
"download": {
|
|
"preparing": "🚀 Preparing your download...",
|
|
"completed": "✅ Download completed!",
|
|
"video_completed": "✅ Video download completed!",
|
|
"audio_completed": "✅ Audio download completed!",
|
|
"subtitle_completed": "✅ Subtitle download completed!",
|
|
"please_set_path": "Please set a download path using 'Change Path'",
|
|
"please_enter_url": "Please enter a URL",
|
|
"please_enter_url_and_path": "Please enter URL and set download path",
|
|
"please_select_format": "Please select a format"
|
|
},
|
|
"formats": {
|
|
"show_formats": "Show formats:"
|
|
}
|
|
}
|
|
|
|
@classmethod
|
|
def _ensure_languages_dir(cls) -> None:
|
|
"""Ensure the languages directory exists."""
|
|
cls._languages_dir.mkdir(exist_ok=True)
|
|
|
|
@classmethod
|
|
def _load_language(cls, language_code: str) -> Dict[str, Any]:
|
|
"""
|
|
Load a language file from disk.
|
|
|
|
Args:
|
|
language_code: The language code (e.g., 'en', 'es')
|
|
|
|
Returns:
|
|
Dictionary containing the language strings, or empty dict if not found
|
|
"""
|
|
language_file = cls._languages_dir / f"{language_code}.json"
|
|
|
|
if not language_file.exists():
|
|
logger.warning(f"Language file not found: {language_file}")
|
|
return {}
|
|
|
|
try:
|
|
with open(language_file, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except (json.JSONDecodeError, OSError) as e:
|
|
logger.error(f"Failed to load language file {language_file}: {e}")
|
|
return {}
|
|
|
|
@classmethod
|
|
def _get_nested_value(cls, data: Dict[str, Any], key: str) -> Any:
|
|
"""
|
|
Get a nested value from dictionary using dot notation.
|
|
|
|
Args:
|
|
data: Dictionary to search in
|
|
key: Dot-separated key (e.g., "app.title")
|
|
|
|
Returns:
|
|
The value if found, None otherwise
|
|
"""
|
|
parts = key.split(".")
|
|
value = data
|
|
|
|
for part in parts:
|
|
if isinstance(value, dict) and part in value:
|
|
value = value[part]
|
|
else:
|
|
return None
|
|
|
|
return value
|
|
|
|
@classmethod
|
|
def get_text(cls, key: str, **kwargs) -> str:
|
|
"""
|
|
Get localized text for the given key.
|
|
|
|
Args:
|
|
key: Dot-separated key for the text (e.g., "app.title")
|
|
**kwargs: Format parameters for the text
|
|
|
|
Returns:
|
|
Localized text, with fallback to English if not found
|
|
"""
|
|
with cls._lock:
|
|
# Load current language if not cached
|
|
if cls._current_language not in cls._languages:
|
|
cls._languages[cls._current_language] = cls._load_language(cls._current_language)
|
|
|
|
# Try to get from current language
|
|
current_lang_data = cls._languages.get(cls._current_language, {})
|
|
text = cls._get_nested_value(current_lang_data, key)
|
|
|
|
# Fallback to embedded English strings
|
|
if text is None:
|
|
text = cls._get_nested_value(cls._fallback_strings, key)
|
|
|
|
# Final fallback to key itself
|
|
if text is None:
|
|
logger.warning(f"Localization key not found: {key}")
|
|
text = key
|
|
|
|
# Format the text with provided parameters
|
|
if kwargs and isinstance(text, str):
|
|
try:
|
|
text = text.format(**kwargs)
|
|
except (KeyError, ValueError) as e:
|
|
logger.warning(f"Failed to format localized text '{key}': {e}")
|
|
|
|
return str(text)
|
|
|
|
@classmethod
|
|
def set_language(cls, language_code: str) -> None:
|
|
"""
|
|
Set the current language.
|
|
|
|
Args:
|
|
language_code: The language code to set (e.g., 'en', 'es')
|
|
"""
|
|
with cls._lock:
|
|
if language_code != cls._current_language:
|
|
cls._current_language = language_code
|
|
# Clear cache to force reload
|
|
cls._languages.clear()
|
|
logger.info(f"Language set to: {language_code}")
|
|
|
|
@classmethod
|
|
def get_current_language(cls) -> str:
|
|
"""Get the current language code."""
|
|
return cls._current_language
|
|
|
|
@classmethod
|
|
def get_available_languages(cls) -> Dict[str, str]:
|
|
"""
|
|
Get available languages from the languages directory.
|
|
|
|
Returns:
|
|
Dictionary mapping language codes to display names
|
|
"""
|
|
cls._ensure_languages_dir()
|
|
|
|
available_languages = {"en": cls.get_text("language.english")}
|
|
|
|
# Scan for language files
|
|
for language_file in cls._languages_dir.glob("*.json"):
|
|
lang_code = language_file.stem
|
|
if lang_code != "en": # Skip English as it's already added
|
|
# Try to get language display name from the file
|
|
lang_data = cls._load_language(lang_code)
|
|
display_name = cls._get_nested_value(lang_data, "language.display_name")
|
|
if display_name:
|
|
available_languages[lang_code] = display_name
|
|
else:
|
|
# Fallback display name
|
|
available_languages[lang_code] = lang_code.upper()
|
|
|
|
return available_languages
|
|
|
|
@classmethod
|
|
def initialize(cls, language_code: str = "en") -> None:
|
|
"""
|
|
Initialize the localization system.
|
|
|
|
Args:
|
|
language_code: Initial language code to use
|
|
"""
|
|
with cls._lock:
|
|
cls._ensure_languages_dir()
|
|
cls.set_language(language_code)
|
|
logger.info(f"Localization system initialized with language: {language_code}")
|
|
|
|
|
|
# Convenience function for getting localized text
|
|
def _(key: str, **kwargs) -> str:
|
|
"""
|
|
Convenience function to get localized text.
|
|
|
|
Args:
|
|
key: Dot-separated key for the text
|
|
**kwargs: Format parameters
|
|
|
|
Returns:
|
|
Localized text
|
|
"""
|
|
return LocalizationManager.get_text(key, **kwargs) |