""" Server-side localization utilities for export templates. This module provides utilities to load localized labels from static/locales/*.json files for use in export templates. """ import json import os import logging from pathlib import Path from typing import Dict, Optional from datetime import datetime logger = logging.getLogger(__name__) # Cache for loaded locales _locale_cache: Dict[str, dict] = {} def get_locales_dir() -> Path: """Get the path to the locales directory.""" # Navigate from src/utils to static/locales base_dir = Path(__file__).parent.parent.parent return base_dir / 'static' / 'locales' def load_locale(language: str) -> dict: """ Load locale data for a given language. Args: language: Language code (e.g., 'en', 'de', 'fr') Returns: Dictionary containing all locale strings """ # Check cache first if language in _locale_cache: return _locale_cache[language] locales_dir = get_locales_dir() locale_file = locales_dir / f'{language}.json' # Fallback to English if requested language doesn't exist if not locale_file.exists(): logger.warning(f"Locale file not found for '{language}', falling back to English") locale_file = locales_dir / 'en.json' language = 'en' try: with open(locale_file, 'r', encoding='utf-8') as f: locale_data = json.load(f) _locale_cache[language] = locale_data return locale_data except (json.JSONDecodeError, IOError) as e: logger.error(f"Error loading locale file '{locale_file}': {e}") # Return empty dict on error return {} def get_export_labels(language: str) -> dict: """ Get localized export labels for a given language. Args: language: Language code (e.g., 'en', 'de', 'fr') Returns: Dictionary containing export-specific labels """ locale_data = load_locale(language) # Get exportLabels section, or fall back to defaults export_labels = locale_data.get('exportLabels', {}) # Default English labels as fallback defaults = { 'metadata': 'Metadata', 'notes': 'Notes', 'summary': 'Summary', 'transcription': 'Transcription', 'date': 'Date', 'created': 'Created', 'originalFile': 'Original File', 'fileSize': 'File Size', 'participants': 'Participants', 'tags': 'Tags', 'transcriptionTime': 'Transcription Time', 'summarizationTime': 'Summarization Time', 'footer': 'Generated with [Speakr](https://github.com/learnedmachine/speakr)' } # Merge defaults with loaded labels result = defaults.copy() result.update(export_labels) return result def format_date_localized(dt: datetime, language: str) -> str: """ Format a datetime in a localized format. Args: dt: The datetime to format language: Language code for localization Returns: Localized date string """ if dt is None: return '' # Define locale-specific date formats date_formats = { 'en': '%B %d, %Y', # January 15, 2026 'de': '%d. %B %Y', # 15. Januar 2026 'fr': '%d %B %Y', # 15 janvier 2026 'es': '%d de %B de %Y', # 15 de enero de 2026 'zh': '%Y年%m月%d日', # 2026年01月15日 'ru': '%d %B %Y г.', # 15 января 2026 г. } # Month names for different languages month_names = { 'de': { 'January': 'Januar', 'February': 'Februar', 'March': 'März', 'April': 'April', 'May': 'Mai', 'June': 'Juni', 'July': 'Juli', 'August': 'August', 'September': 'September', 'October': 'Oktober', 'November': 'November', 'December': 'Dezember' }, 'fr': { 'January': 'janvier', 'February': 'février', 'March': 'mars', 'April': 'avril', 'May': 'mai', 'June': 'juin', 'July': 'juillet', 'August': 'août', 'September': 'septembre', 'October': 'octobre', 'November': 'novembre', 'December': 'décembre' }, 'es': { 'January': 'enero', 'February': 'febrero', 'March': 'marzo', 'April': 'abril', 'May': 'mayo', 'June': 'junio', 'July': 'julio', 'August': 'agosto', 'September': 'septiembre', 'October': 'octubre', 'November': 'noviembre', 'December': 'diciembre' }, 'ru': { 'January': 'января', 'February': 'февраля', 'March': 'марта', 'April': 'апреля', 'May': 'мая', 'June': 'июня', 'July': 'июля', 'August': 'августа', 'September': 'сентября', 'October': 'октября', 'November': 'ноября', 'December': 'декабря' } } # Get format for language, default to English date_format = date_formats.get(language, date_formats['en']) # Format the date formatted = dt.strftime(date_format) # Replace English month names with localized versions if language in month_names: for eng, local in month_names[language].items(): formatted = formatted.replace(eng, local) return formatted def format_datetime_localized(dt: datetime, language: str) -> str: """ Format a datetime with time in a localized format. Args: dt: The datetime to format language: Language code for localization Returns: Localized datetime string """ if dt is None: return '' date_part = format_date_localized(dt, language) # Time format varies by language time_formats = { 'en': '%I:%M %p', # 02:30 PM 'de': '%H:%M Uhr', # 14:30 Uhr 'fr': '%H:%M', # 14:30 'es': '%H:%M', # 14:30 'zh': '%H:%M', # 14:30 'ru': '%H:%M', # 14:30 } time_format = time_formats.get(language, time_formats['en']) time_part = dt.strftime(time_format) # Combine with appropriate connector connectors = { 'en': ' at ', 'de': ' um ', 'fr': ' à ', 'es': ' a las ', 'zh': ' ', 'ru': ' в ', } connector = connectors.get(language, ' at ') return f"{date_part}{connector}{time_part}" def clear_locale_cache(): """Clear the locale cache (useful for testing or hot-reloading).""" global _locale_cache _locale_cache = {}