mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-10-04 16:26:18 +00:00
302 lines
9.1 KiB
Python
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()
|