diff --git a/i18n_key_sync/validate.py b/i18n_key_sync/validate.py new file mode 100644 index 0000000..905e7d5 --- /dev/null +++ b/i18n_key_sync/validate.py @@ -0,0 +1,122 @@ +"""Validation of i18n keys against locale files.""" + +from dataclasses import dataclass, field +from typing import Dict, Set + + +@dataclass +class ValidationResult: + """Result of validating i18n keys against locale files.""" + + missing_keys: Set[str] = field(default_factory=set) + unused_keys: Set[str] = field(default_factory=set) + matching_keys: Set[str] = field(default_factory=set) + extra_keys_per_locale: Dict[str, Set[str]] = field(default_factory=dict) + + @property + def total_missing(self) -> int: + """Total count of missing keys.""" + return len(self.missing_keys) + + @property + def total_unused(self) -> int: + """Total count of unused keys.""" + return len(self.unused_keys) + + @property + def total_matching(self) -> int: + """Total count of matching keys.""" + return len(self.matching_keys) + + @property + def is_valid(self) -> bool: + """Check if there are no missing or unused keys.""" + return len(self.missing_keys) == 0 and len(self.unused_keys) == 0 + + +class Validator: + """Validate extracted i18n keys against locale files.""" + + def __init__(self): + """Initialize the validator.""" + + def validate( + self, + extracted_keys: Set[str], + locale_keys: Dict[str, Set[str]], + ) -> ValidationResult: + """Validate extracted keys against locale keys. + + Args: + extracted_keys: Set of keys extracted from source code. + locale_keys: Dictionary mapping locale names to their key sets. + + Returns: + ValidationResult with missing, unused, and matching keys. + + """ + if not locale_keys: + return ValidationResult( + missing_keys=extracted_keys, + unused_keys=set(), + matching_keys=set(), + ) + + result = ValidationResult() + + for locale, keys in locale_keys.items(): + locale_missing = extracted_keys - keys + locale_unused = keys - extracted_keys + locale_matching = keys & extracted_keys + + result.missing_keys.update(locale_missing) + result.unused_keys.update(locale_unused) + result.matching_keys.update(locale_matching) + result.extra_keys_per_locale[locale] = locale_unused + + return result + + def validate_for_locale( + self, + extracted_keys: Set[str], + locale_keys: Set[str], + locale_name: str = "", + ) -> ValidationResult: + """Validate keys against a single locale. + + Args: + extracted_keys: Set of keys extracted from source code. + locale_keys: Set of keys in the locale file. + locale_name: Name of the locale (for reporting). + + Returns: + ValidationResult for the single locale. + + """ + missing_keys = extracted_keys - locale_keys + unused_keys = locale_keys - extracted_keys + matching_keys = locale_keys & extracted_keys + + return ValidationResult( + missing_keys=missing_keys, + unused_keys=unused_keys, + matching_keys=matching_keys, + ) + + def get_summary(self, result: ValidationResult) -> Dict[str, int]: + """Get a summary of the validation result. + + Args: + result: ValidationResult to summarize. + + Returns: + Dictionary with summary statistics. + + """ + return { + "missing_keys": len(result.missing_keys), + "unused_keys": len(result.unused_keys), + "matching_keys": len(result.matching_keys), + "total_keys_in_code": len(result.missing_keys) + len(result.matching_keys), + "total_keys_in_locale": len(result.unused_keys) + len(result.matching_keys), + }