Source code for kokorog2p.fr.numbers

"""French number to words conversion using num2words."""

import re
from collections.abc import Callable

# Try to import num2words
_num2words_fn: Callable[..., str] | None = None
try:
    from num2words import num2words as _n2w

    _num2words_fn = _n2w
    NUM2WORDS_AVAILABLE = True
except ImportError:
    NUM2WORDS_AVAILABLE = False


[docs] def number_to_french(n: int, ordinal: bool = False) -> str: """Convert a number to French words using num2words. Args: n: Integer to convert. ordinal: If True, return ordinal form (premier, deuxième, etc.) Returns: French word representation. Raises: ImportError: If num2words is not installed. Example: >>> number_to_french(42) 'quarante-deux' >>> number_to_french(1, ordinal=True) 'premier' """ if _num2words_fn is None: raise ImportError( "num2words is required for number conversion. " "Install with: pip install num2words" ) if ordinal: return _num2words_fn(n, lang="fr", to="ordinal") return _num2words_fn(n, lang="fr")
[docs] def expand_numbers(text: str, max_value: int = 1000000) -> str: """Expand numbers in text to French words. Args: text: Text containing numbers. max_value: Maximum value to expand (larger numbers kept as-is). Returns: Text with numbers expanded. Example: >>> expand_numbers("J'ai 3 pommes et 42 oranges.") "J'ai trois pommes et quarante-deux oranges." """ if not NUM2WORDS_AVAILABLE: return text def replace_match(match: re.Match[str]) -> str: num = int(match.group(0)) if num <= max_value: return number_to_french(num) return match.group(0) return re.sub(r"\b\d+\b", replace_match, text)
[docs] def expand_time(text: str) -> str: """Expand time expressions like 14h30. Args: text: Text containing time expressions. Returns: Text with times expanded. Example: >>> expand_time("Le rendez-vous est à 14h30.") 'Le rendez-vous est à quatorze heures trente.' """ if not NUM2WORDS_AVAILABLE: return text def replace_time(match: re.Match[str]) -> str: hours = int(match.group(1)) minutes = match.group(2) result = number_to_french(hours) + " heure" if hours > 1: result += "s" if minutes: mins = int(minutes) if mins > 0: result += " " + number_to_french(mins) return result return re.sub(r"\b(\d{1,2})h(\d{2})?\b", replace_time, text)
[docs] def expand_currency(text: str) -> str: """Expand currency amounts. Args: text: Text containing currency amounts. Returns: Text with currency expanded. Example: >>> expand_currency("Ça coûte 5€.") 'Ça coûte cinq euros.' """ if not NUM2WORDS_AVAILABLE: return text # Euro text = re.sub( r"(\d+)\s*€", lambda m: number_to_french(int(m.group(1))) + (" euro" if int(m.group(1)) == 1 else " euros"), text, ) # Dollar text = re.sub( r"\$\s*(\d+)", lambda m: number_to_french(int(m.group(1))) + (" dollar" if int(m.group(1)) == 1 else " dollars"), text, ) return text
[docs] def expand_ordinal(text: str) -> str: """Expand ordinal numbers like 1er, 2ème, etc. Args: text: Text containing ordinal numbers. Returns: Text with ordinals expanded. Example: >>> expand_ordinal("Le 1er janvier") 'Le premier janvier' """ if not NUM2WORDS_AVAILABLE: return text # Match patterns like 1er, 1ère, 2e, 2ème, 2nd, 2nde, etc. def replace_ordinal(match: re.Match[str]) -> str: num = int(match.group(1)) suffix = match.group(2).lower() # Handle feminine forms if suffix in ("ère", "re", "nde"): # For feminine, we need to modify the output ordinal = number_to_french(num, ordinal=True) # Convert masculine to feminine if ordinal.endswith("premier"): ordinal = ordinal[:-7] + "première" elif ordinal.endswith("second"): ordinal = ordinal[:-6] + "seconde" return ordinal return number_to_french(num, ordinal=True) # Match ordinal patterns text = re.sub( r"\b(\d+)(er|ère|re|e|ème|nd|nde)\b", replace_ordinal, text, flags=re.IGNORECASE, ) return text
[docs] def is_available() -> bool: """Check if num2words is available. Returns: True if num2words is installed. """ return NUM2WORDS_AVAILABLE