Source code for unipress.core.messages

"""
Message loading system for internationalization.

Handles loading and merging of JSON message files with fallback support.
"""

import json
from pathlib import Path
from typing import Any


[docs] class MessageLoader: """Loads and manages localized messages from JSON files."""
[docs] def __init__(self, language: str, game_name: str): """ Initialize message loader. Args: language: Language code (e.g., "pl_PL", "en_US") game_name: Name of the game for game-specific messages """ self.language = language self.game_name = game_name self.messages: dict[str, Any] = {} self.fallback_language = "pl_PL" # Polish as default fallback # Load messages with fallback self._load_messages()
[docs] def _load_json_file(self, file_path: Path) -> dict[str, Any]: """Load JSON file safely, return empty dict if file doesn't exist.""" try: with open(file_path, encoding="utf-8") as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): return {}
[docs] def _merge_messages( self, base: dict[str, Any], override: dict[str, Any] ) -> dict[str, Any]: """Merge two message dictionaries recursively.""" result = base.copy() for key, value in override.items(): if ( key in result and isinstance(result[key], dict) and isinstance(value, dict) ): result[key] = self._merge_messages(result[key], value) else: result[key] = value return result
[docs] def _load_messages(self) -> None: """Load messages for the specified language with fallback.""" # Base path for locales locales_path = Path("unipress") / "locales" # Try to load fallback messages first (pl_PL) if self.language != self.fallback_language: fallback_messages = self._load_language_messages( locales_path, self.fallback_language ) self.messages = fallback_messages # Load requested language messages (will override fallback) language_messages = self._load_language_messages(locales_path, self.language) if language_messages: if self.messages: self.messages = self._merge_messages(self.messages, language_messages) else: self.messages = language_messages elif not self.messages: # If no messages loaded at all, try to load fallback self.messages = self._load_language_messages( locales_path, self.fallback_language )
[docs] def _load_language_messages( self, locales_path: Path, language: str ) -> dict[str, Any]: """Load all message files for a specific language.""" language_path = locales_path / language messages = {} # Load common messages common_file = language_path / "common.json" common_messages = self._load_json_file(common_file) if common_messages: messages = self._merge_messages(messages, common_messages) # Load game-specific messages game_file = language_path / "games" / f"{self.game_name}.json" game_messages = self._load_json_file(game_file) if game_messages: messages = self._merge_messages(messages, game_messages) return messages
[docs] def get_message(self, key: str, **kwargs) -> str: """ Get localized message with parameter substitution. Args: key: Dot-separated message key (e.g., "ui.score") **kwargs: Parameters for string formatting Returns: Formatted message string, or the key itself if not found """ # Navigate through nested dictionary using dot notation keys = key.split(".") value = self.messages for k in keys: if isinstance(value, dict) and k in value: value = value[k] else: # Return key as fallback if message not found return key # If we found a string, format it with provided parameters if isinstance(value, str): try: return value.format(**kwargs) except (KeyError, ValueError): # If formatting fails, return unformatted string return value # If value is not a string, return the key return key
[docs] def has_message(self, key: str) -> bool: """Check if a message key exists.""" keys = key.split(".") value = self.messages for k in keys: if isinstance(value, dict) and k in value: value = value[k] else: return False return isinstance(value, str)
[docs] def get_available_languages(self) -> list[str]: """Get list of available language codes.""" locales_path = Path("unipress") / "locales" if not locales_path.exists(): return [self.fallback_language] languages = [] for path in locales_path.iterdir(): if path.is_dir() and (path / "common.json").exists(): languages.append(path.name) return sorted(languages)
[docs] def reload(self, language: str = None, game_name: str = None) -> None: """Reload messages with optionally different language or game.""" if language is not None: self.language = language if game_name is not None: self.game_name = game_name self.messages = {} self._load_messages()
[docs] def load_messages(language: str, game_name: str) -> MessageLoader: """ Convenience function to create and return a MessageLoader. Args: language: Language code (e.g., "pl_PL", "en_US") game_name: Name of the game Returns: Configured MessageLoader instance """ return MessageLoader(language, game_name)