Spaces:
Paused
Paused
Upload locale_manager.py
Browse files- config/locale_manager.py +163 -0
config/locale_manager.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# locale_manager.py
|
| 2 |
+
"""
|
| 3 |
+
Locale Manager - Manages locale files for the system
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Dict, List, Optional
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
import sys
|
| 11 |
+
from logger import log_info, log_error, log_debug, log_warning
|
| 12 |
+
|
| 13 |
+
class LocaleManager:
|
| 14 |
+
"""Manages locale files for TTS preprocessing and system-wide language support"""
|
| 15 |
+
|
| 16 |
+
_cache: Dict[str, Dict] = {}
|
| 17 |
+
_available_locales: Optional[List[Dict[str, str]]] = None
|
| 18 |
+
|
| 19 |
+
@classmethod
|
| 20 |
+
def get_locale(cls, language: str) -> Dict:
|
| 21 |
+
"""Get locale data with caching"""
|
| 22 |
+
if language not in cls._cache:
|
| 23 |
+
cls._cache[language] = cls._load_locale(language)
|
| 24 |
+
return cls._cache[language]
|
| 25 |
+
|
| 26 |
+
@classmethod
|
| 27 |
+
def _load_locale(cls, language: str) -> Dict:
|
| 28 |
+
"""Load locale from file - accepts both 'tr' and 'tr-TR' formats"""
|
| 29 |
+
base_path = Path(__file__).parent / "locales"
|
| 30 |
+
|
| 31 |
+
# First try exact match
|
| 32 |
+
locale_file = base_path / f"{language}.json"
|
| 33 |
+
|
| 34 |
+
# If not found and has region code, try without region (tr-TR -> tr)
|
| 35 |
+
if not locale_file.exists() and '-' in language:
|
| 36 |
+
language_code = language.split('-')[0]
|
| 37 |
+
locale_file = base_path / f"{language_code}.json"
|
| 38 |
+
|
| 39 |
+
if locale_file.exists():
|
| 40 |
+
try:
|
| 41 |
+
with open(locale_file, 'r', encoding='utf-8') as f:
|
| 42 |
+
data = json.load(f)
|
| 43 |
+
log_debug(f"✅ Loaded locale file: {locale_file.name}")
|
| 44 |
+
return data
|
| 45 |
+
except Exception as e:
|
| 46 |
+
log_error(f"Failed to load locale file {locale_file}", e)
|
| 47 |
+
|
| 48 |
+
# Try English fallback
|
| 49 |
+
fallback_file = base_path / "en.json"
|
| 50 |
+
if fallback_file.exists():
|
| 51 |
+
try:
|
| 52 |
+
with open(fallback_file, 'r', encoding='utf-8') as f:
|
| 53 |
+
data = json.load(f)
|
| 54 |
+
log_warning(f"⚠️ Using English fallback for locale: {language}")
|
| 55 |
+
return data
|
| 56 |
+
except:
|
| 57 |
+
pass
|
| 58 |
+
|
| 59 |
+
# Minimal fallback if no locale files exist
|
| 60 |
+
log_warning(f"⚠️ No locale files found, using minimal fallback")
|
| 61 |
+
return {
|
| 62 |
+
"code": "tr",
|
| 63 |
+
"locale_tag": "tr-TR",
|
| 64 |
+
"name": "Türkçe",
|
| 65 |
+
"english_name": "Turkish"
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
@classmethod
|
| 69 |
+
def list_available_locales(cls) -> List[str]:
|
| 70 |
+
"""List all available locale files"""
|
| 71 |
+
base_path = Path(__file__).parent / "locales"
|
| 72 |
+
if not base_path.exists():
|
| 73 |
+
return ["en", "tr"] # Default locales
|
| 74 |
+
return [f.stem for f in base_path.glob("*.json")]
|
| 75 |
+
|
| 76 |
+
@classmethod
|
| 77 |
+
def get_available_locales_with_names(cls) -> List[Dict[str, str]]:
|
| 78 |
+
"""Get list of all available locales with their display names"""
|
| 79 |
+
if cls._available_locales is not None:
|
| 80 |
+
return cls._available_locales
|
| 81 |
+
|
| 82 |
+
cls._available_locales = []
|
| 83 |
+
base_path = Path(__file__).parent / "locales"
|
| 84 |
+
|
| 85 |
+
if not base_path.exists():
|
| 86 |
+
# Return default locales if directory doesn't exist
|
| 87 |
+
cls._available_locales = [
|
| 88 |
+
{
|
| 89 |
+
"code": "tr-TR",
|
| 90 |
+
"name": "Türkçe",
|
| 91 |
+
"english_name": "Turkish"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"code": "en-US",
|
| 95 |
+
"name": "English",
|
| 96 |
+
"english_name": "English (US)"
|
| 97 |
+
}
|
| 98 |
+
]
|
| 99 |
+
return cls._available_locales
|
| 100 |
+
|
| 101 |
+
# Load all locale files
|
| 102 |
+
for locale_file in base_path.glob("*.json"):
|
| 103 |
+
try:
|
| 104 |
+
locale_code = locale_file.stem
|
| 105 |
+
locale_data = cls.get_locale(locale_code)
|
| 106 |
+
|
| 107 |
+
cls._available_locales.append({
|
| 108 |
+
"code": locale_code,
|
| 109 |
+
"name": locale_data.get("name", locale_code),
|
| 110 |
+
"english_name": locale_data.get("english_name", locale_code)
|
| 111 |
+
})
|
| 112 |
+
|
| 113 |
+
log_info(f"✅ Loaded locale: {locale_code} - {locale_data.get('name', 'Unknown')}")
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
log_error(f"❌ Failed to load locale {locale_file}", e)
|
| 117 |
+
|
| 118 |
+
# Sort by name for consistent ordering
|
| 119 |
+
cls._available_locales.sort(key=lambda x: x['name'])
|
| 120 |
+
|
| 121 |
+
return cls._available_locales
|
| 122 |
+
|
| 123 |
+
@classmethod
|
| 124 |
+
def get_locale_details(cls, locale_code: str) -> Optional[Dict]:
|
| 125 |
+
"""Get detailed info for a specific locale"""
|
| 126 |
+
try:
|
| 127 |
+
locale_data = cls.get_locale(locale_code)
|
| 128 |
+
# Ensure code is in the data
|
| 129 |
+
locale_data['code'] = locale_code
|
| 130 |
+
return locale_data
|
| 131 |
+
except:
|
| 132 |
+
return None
|
| 133 |
+
|
| 134 |
+
@classmethod
|
| 135 |
+
def is_locale_supported(cls, locale_code: str) -> bool:
|
| 136 |
+
"""Check if a locale is supported system-wide"""
|
| 137 |
+
available_codes = [locale['code'] for locale in cls.get_available_locales_with_names()]
|
| 138 |
+
return locale_code in available_codes
|
| 139 |
+
|
| 140 |
+
@classmethod
|
| 141 |
+
def validate_project_languages(cls, languages: List[str]) -> List[str]:
|
| 142 |
+
"""Validate that all languages are system-supported, return invalid ones"""
|
| 143 |
+
available_codes = [locale['code'] for locale in cls.get_available_locales_with_names()]
|
| 144 |
+
invalid_languages = [
|
| 145 |
+
lang for lang in languages
|
| 146 |
+
if lang not in available_codes
|
| 147 |
+
]
|
| 148 |
+
return invalid_languages
|
| 149 |
+
|
| 150 |
+
@classmethod
|
| 151 |
+
def get_default_locale(cls) -> str:
|
| 152 |
+
"""Get system default locale"""
|
| 153 |
+
available_locales = cls.get_available_locales_with_names()
|
| 154 |
+
|
| 155 |
+
# Priority: tr-TR, en-US, first available
|
| 156 |
+
for preferred in ["tr-TR", "en-US"]:
|
| 157 |
+
if any(locale['code'] == preferred for locale in available_locales):
|
| 158 |
+
return preferred
|
| 159 |
+
|
| 160 |
+
# Return first available or fallback
|
| 161 |
+
if available_locales:
|
| 162 |
+
return available_locales[0]['code']
|
| 163 |
+
return "en-US"
|