Files
WGDashboard/src/static/locales/locale_manager.py
2025-08-20 15:10:48 +08:00

302 lines
9.1 KiB
Python

#!/usr/bin/env python3
import json
import sys
from pathlib import Path
from typing import Dict, List, Tuple
from dataclasses import dataclass
@dataclass
class Language:
"""Represents a language configuration."""
lang_id: str
lang_name: str
@classmethod
def from_dict(cls, data: Dict) -> 'Language':
"""Create Language instance from dictionary."""
return cls(
lang_id=data.get('lang_id', ''),
lang_name=data.get('lang_name', '')
)
@dataclass
class TranslationStats:
"""Statistics about translation verification."""
missing_count: int
deprecated_count: int
total_keys: int
@property
def completion_percentage(self) -> float:
"""Calculate completion percentage."""
if self.total_keys == 0:
return 0.0
completed = self.total_keys - self.missing_count
return (completed / self.total_keys) * 100
class LocaleManager:
"""Manages locale files and translation verification."""
SUPPORTED_LOCALES_FILE = "supported_locales.json"
LOCALE_TEMPLATE_FILE = "locale_template.json"
def __init__(self):
self.supported_locales: List[Language] = []
self.locale_template: Dict[str, str] = {}
self._load_configuration()
def _load_configuration(self) -> None:
"""Load active languages and template configuration."""
try:
self.supported_locales = self._load_supported_locales()
self.locale_template = self._load_locale_template()
except FileNotFoundError as e:
print(f"[✗] Configuration file not found: {e}")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"[✗] Invalid JSON in configuration file: {e}")
sys.exit(1)
def _load_supported_locales(self) -> List[Language]:
"""Load active languages from JSON file."""
config_path = Path(self.SUPPORTED_LOCALES_FILE)
if not config_path.exists():
raise FileNotFoundError(
f"Active languages file not found: {config_path}"
)
with open(config_path, 'r', encoding='utf-8') as file:
data = json.load(file)
return [Language.from_dict(lang_data) for lang_data in data]
def _load_locale_template(self) -> Dict[str, str]:
"""Load language template from JSON file."""
template_path = Path(self.LOCALE_TEMPLATE_FILE)
if not template_path.exists():
raise FileNotFoundError(
f"Language template file not found: {template_path}"
)
with open(template_path, 'r', encoding='utf-8') as file:
return json.load(file)
def _load_language_file(self, lang_id: str) -> Dict[str, str]:
"""Load specific language file."""
lang_path = Path(f"{lang_id}.json")
if not lang_path.exists():
raise FileNotFoundError(f"Language file not found: {lang_path}")
with open(lang_path, 'r', encoding='utf-8') as file:
return json.load(file)
def _save_language_file(self, lang_id: str, lang_data: Dict[str, str]) -> None:
"""Save language file with proper formatting."""
lang_path = Path(f"{lang_id}.json")
with open(lang_path, 'w', encoding='utf-8') as file:
json.dump(
lang_data,
file,
ensure_ascii=False,
indent='\t',
sort_keys=True
)
def get_language_ids(self) -> List[str]:
"""Get list of all available language IDs."""
return [lang.lang_id for lang in self.supported_locales]
def validate_language_id(self, lang_id: str) -> Tuple[bool, str]:
"""
Validate language ID.
Returns:
Tuple of (is_valid, error_message)
"""
available_ids = self.get_language_ids()
if lang_id not in available_ids:
return False, f"'{lang_id}' is not a valid language ID"
return True, ""
def analyze_translations(self, lang_id: str) -> Tuple[List[str], List[str], TranslationStats]:
"""
Analyze translation file for missing and deprecated keys.
Returns:
Tuple of (missing_keys, deprecated_keys, stats)
"""
try:
lang_file = self._load_language_file(lang_id)
except FileNotFoundError:
print(
f"[!] Language file {lang_id}.json not found. Creating new file..."
)
lang_file = {}
# Find missing translations
missing_translations = [
key for key in self.locale_template
if key not in lang_file or not lang_file[key].strip()
]
# Find deprecated translations
deprecated_translations = [
key for key in lang_file
if key not in self.locale_template
]
# Calculate statistics
stats = TranslationStats(
missing_count=len(missing_translations),
deprecated_count=len(deprecated_translations),
total_keys=len(self.locale_template)
)
return missing_translations, deprecated_translations, stats
def fix_translation_file(self, lang_id: str) -> TranslationStats:
"""
Fix translation file by adding missing keys and removing deprecated ones.
Returns:
TranslationStats with the changes made
"""
try:
lang_file = self._load_language_file(lang_id)
except FileNotFoundError:
lang_file = {}
missing_translations, deprecated_translations, stats = self.analyze_translations(
lang_id
)
# Create new language file
new_lang_file = dict(lang_file)
# Add missing translations with empty values
for key in missing_translations:
new_lang_file[key] = ""
# Remove deprecated translations
for key in deprecated_translations:
new_lang_file.pop(key, None)
# Save updated file
self._save_language_file(lang_id, new_lang_file)
return stats
def display_header(self) -> None:
"""Display application header."""
title = "WGDashboard Locale File Manager [by @donaldzou]"
border = "=" * (len(title) + 4)
print(border)
print(f"| {title} |")
print(border)
print()
def display_available_languages(self) -> None:
"""Display available languages in a formatted table."""
print("[i] Available languages")
print("-" * 50)
for lang in self.supported_locales:
print(f"{lang.lang_name:<25} | {lang.lang_id}")
print()
def display_translation_results(self, lang_id: str, stats: TranslationStats) -> None:
"""Display translation verification results."""
print(f"[#] Translation analysis for '{lang_id}'")
print("-" * 50)
print(f" [-] Total keys: {stats.total_keys}")
print(f" [✗] Missing translations: {stats.missing_count}")
print(f" [*] Deprecated translations: {stats.deprecated_count}")
print(f" [✓] Completion: {stats.completion_percentage:.1f}%")
if stats.missing_count > 0 or stats.deprecated_count > 0:
print(f"\n [i] File {lang_id}.json has been updated:")
print(f" • Missing translations added (empty values)")
print(f" • Deprecated translations removed")
else:
print(f"\n Perfect! No issues found in {lang_id}.json")
print()
def get_user_language_choice(self) -> str:
"""Get language choice from user with validation."""
while True:
try:
lang_id = input("[ENTER] Language ID to verify: ").strip()
if not lang_id:
print(" [!] Please enter a valid language ID")
continue
is_valid, error_msg = self.validate_language_id(lang_id)
if not is_valid:
print(f" [✗] {error_msg}")
continue
return lang_id
except KeyboardInterrupt:
print("\n\n[EXIT] Operation cancelled by user")
sys.exit(0)
except EOFError:
print("\n\n[EXIT] Goodbye!")
sys.exit(0)
def run(self) -> None:
"""Main application loop."""
try:
self.display_header()
self.display_available_languages()
while True:
lang_id = self.get_user_language_choice()
print(f"\n[>] Verifying language file: {lang_id}.json")
print("=" * 50)
stats = self.fix_translation_file(lang_id)
self.display_translation_results(lang_id, stats)
except KeyboardInterrupt:
print("\n\n[EXIT] Operation cancelled by user")
sys.exit(0)
except Exception as e:
print(f"\n[✗] Unexpected error: {e}")
sys.exit(1)
def main() -> None:
"""Entry point of the application."""
locale_manager = LocaleManager()
locale_manager.run()
if __name__ == "__main__":
main()