"""Locale file parsing for JSON and YAML format.""" import json from pathlib import Path from typing import Any, Dict, List, Optional, Set class LocaleParser: """Parse and manage locale files in JSON and YAML formats.""" def __init__(self): """Initialize the locale parser.""" self._cache: Dict[str, Dict[str, Any]] = {} def parse_file(self, file_path: Path | str) -> Dict[str, Any]: """Parse a single locale file. Args: file_path: Path to the locale file. Returns: Dictionary of locale keys and values. Raises: ValueError: If the file format is not supported. FileNotFoundError: If the file doesn't exist. """ path = Path(file_path) path_str = str(path) if path_str in self._cache: return self._cache[path_str] content = path.read_text(encoding="utf-8") suffix = path.suffix.lower() if suffix == ".json": data = json.loads(content) elif suffix in [".yaml", ".yml"]: import yaml data = yaml.safe_load(content) else: raise ValueError(f"Unsupported locale file format: {suffix}") if not isinstance(data, dict): data = {} self._cache[str(file_path)] = data return data def get_all_keys(self, data: Dict[str, Any], prefix: str = "") -> Set[str]: """Get all keys from nested dictionary, including nested keys as dot notation. Args: data: Nested dictionary of locale data. prefix: Prefix for nested keys. Returns: Set of all keys in dot notation. """ keys = set() for key, value in data.items(): full_key = f"{prefix}.{key}" if prefix else key if isinstance(value, dict): keys.update(self.get_all_keys(value, full_key)) else: keys.add(full_key) return keys def flatten_keys(self, data: Dict[str, Any], prefix: str = "") -> Dict[str, Any]: """Flatten nested dictionary to dot notation. Args: data: Nested dictionary of locale data. prefix: Prefix for nested keys. Returns: Flattened dictionary with dot notation keys. """ flattened = {} for key, value in data.items(): full_key = f"{prefix}.{key}" if prefix else key if isinstance(value, dict): flattened.update(self.flatten_keys(value, full_key)) else: flattened[full_key] = value return flattened def unflatten_keys(self, data: Dict[str, Any]) -> Dict[str, Any]: """Unflatten dot notation keys to nested dictionary. Args: data: Flattened dictionary with dot notation keys. Returns: Nested dictionary. """ nested: Dict[str, Any] = {} for key, value in data.items(): parts = key.split(".") current = nested for part in parts[:-1]: if part not in current: current[part] = {} current = current[part] current[parts[-1]] = value return nested def parse_directory( self, dir_path: str, locales: Optional[List[str]] = None, ) -> Dict[str, Set[str]]: """Parse all locale files in a directory. Args: dir_path: Path to the locale directory. locales: Optional list of locale names to include. Returns: Dictionary mapping locale names to their key sets. """ locale_dir = Path(dir_path) results: Dict[str, Set[str]] = {} if not locale_dir.exists(): return results for locale_file in locale_dir.iterdir(): if not locale_file.is_file(): continue locale_name = locale_file.stem if locales and locale_name not in locales: continue try: data = self.parse_file(locale_file) keys = self.get_all_keys(data) results[locale_name] = keys except (ValueError, json.JSONDecodeError) as e: if locales: raise ValueError(f"Error parsing {locale_file}: {e}") return results def write_file( self, file_path: Path, data: Dict[str, Any], format: str = "json", ) -> None: """Write locale data to a file. Args: file_path: Path to the output file. data: Dictionary of locale data. format: Output format (json or yaml). """ content: str if format == "json": content = json.dumps(data, indent=2, ensure_ascii=False) elif format in ["yaml", "yml"]: import yaml content = yaml.dump(data, default_flow_style=False, allow_unicode=True) else: raise ValueError(f"Unsupported format: {format}") file_path.write_text(content, encoding="utf-8") def add_key( self, data: Dict[str, Any], key: str, value: Any, ) -> Dict[str, Any]: """Add a key to locale data, creating nested structure if needed. Args: data: Existing locale data. key: Key in dot notation. value: Value to set. Returns: Updated locale data. """ parts = key.split(".") current = data for part in parts[:-1]: if part not in current: current[part] = {} current = current[part] current[parts[-1]] = value return data def get_value(self, data: Dict[str, Any], key: str) -> Optional[Any]: """Get a value from locale data using dot notation. Args: data: Locale data dictionary. key: Key in dot notation. Returns: The value if found, None otherwise. """ parts = key.split(".") current = data for part in parts: if isinstance(current, dict) and part in current: current = current[part] else: return None return current