Initial release: DictIA v0.8.14-alpha (fork de Speakr, AGPL-3.0)
This commit is contained in:
214
src/utils/localization.py
Normal file
214
src/utils/localization.py
Normal file
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
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 = {}
|
||||
Reference in New Issue
Block a user