mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-20 13:20:38 -05:00
d9c6192884
Add complete internationalization (i18n) infrastructure supporting 9 languages including full Right-to-Left (RTL) support for Arabic and Hebrew. Languages supported: - English, German, French, Spanish, Dutch, Italian, Finnish (LTR) - Arabic, Hebrew (RTL with complete layout support) Core features: * Flask-Babel configuration with locale selector * Translation files for all 9 languages (480+ strings each) * Language selector UI component in header with globe icon * User language preference storage in database * RTL CSS support with automatic layout reversal * Session and user-based language persistence Model field translation system: * Created comprehensive i18n helper utilities (app/utils/i18n_helpers.py) * 17 new Jinja2 template filters for automatic translation * Support for task statuses, priorities, project statuses, invoice statuses, payment methods, expense categories, and all model enum fields * Status badge CSS classes for consistent styling Technical implementation: * Language switching via API endpoint (POST /api/language) * Direct language switching route (GET /set-language/<lang>) * RTL detection and automatic dir="rtl" attribute * Context processors for language information in all templates * Template filters registered globally Testing and quality: * 50+ unit tests covering all i18n functionality * Tests for locale selection, language switching, RTL detection * Comprehensive test coverage for all translation features Files added: - translations/es/LC_MESSAGES/messages.po (Spanish) - translations/ar/LC_MESSAGES/messages.po (Arabic) - translations/he/LC_MESSAGES/messages.po (Hebrew) - app/utils/i18n_helpers.py (translation helper functions) - app/static/css/rtl-support.css (RTL layout support) - tests/test_i18n.py (comprehensive test suite) - scripts/audit_i18n.py (translation audit tool) Files modified: - app/config.py: Added 3 languages + RTL configuration - app/routes/user.py: Language switching endpoints - app/templates/base.html: Language selector + RTL support - app/utils/context_processors.py: Language context injection - app/__init__.py: Registered i18n template filters - scripts/extract_translations.py: Updated language list - translations/*/messages.po: Added 70+ model field translations The infrastructure is production-ready. Model enum fields now automatically translate in templates using the new filters. Flash messages and some template strings remain in English until wrapped with translation markers (tracked separately for incremental implementation).
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
from flask import g, request, current_app
|
|
from flask_babel import get_locale
|
|
from flask_login import current_user
|
|
from app.models import Settings
|
|
from app.utils.timezone import get_timezone_offset_for_timezone
|
|
|
|
def register_context_processors(app):
|
|
"""Register context processors for the application"""
|
|
|
|
# Register permission helpers for templates
|
|
from app.utils.permissions import init_permission_helpers
|
|
init_permission_helpers(app)
|
|
|
|
@app.context_processor
|
|
def inject_settings():
|
|
"""Inject settings into all templates"""
|
|
try:
|
|
from app import db
|
|
# Check if we have an active database session
|
|
if db.session.is_active:
|
|
settings = Settings.get_settings()
|
|
return {
|
|
'settings': settings,
|
|
'currency': settings.currency,
|
|
'timezone': settings.timezone
|
|
}
|
|
except Exception as e:
|
|
# Log the error but continue with defaults
|
|
print(f"Warning: Could not inject settings: {e}")
|
|
pass
|
|
|
|
# Return defaults if settings not available
|
|
return {
|
|
'settings': None,
|
|
'currency': 'EUR',
|
|
'timezone': 'Europe/Rome'
|
|
}
|
|
|
|
@app.context_processor
|
|
def inject_globals():
|
|
"""Inject global variables into all templates"""
|
|
try:
|
|
from app import db
|
|
# Check if we have an active database session
|
|
if db.session.is_active:
|
|
settings = Settings.get_settings()
|
|
timezone_name = settings.timezone if settings else 'Europe/Rome'
|
|
else:
|
|
timezone_name = 'Europe/Rome'
|
|
except Exception as e:
|
|
# Log the error but continue with defaults
|
|
print(f"Warning: Could not inject globals: {e}")
|
|
timezone_name = 'Europe/Rome'
|
|
|
|
# Determine app version from environment or config
|
|
try:
|
|
import os
|
|
from app.config import Config
|
|
env_version = os.getenv('APP_VERSION')
|
|
# If running in GitHub Actions build, prefer tag-like versions
|
|
version_value = env_version or getattr(Config, 'APP_VERSION', None) or 'dev-0'
|
|
# Strip any leading 'v' prefix to avoid double 'v' in template (e.g., vv3.5.0)
|
|
if version_value and version_value.startswith('v'):
|
|
version_value = version_value[1:]
|
|
except Exception:
|
|
version_value = 'dev-0'
|
|
|
|
# Current locale code (e.g., 'en', 'de')
|
|
try:
|
|
current_locale = str(get_locale())
|
|
except Exception:
|
|
current_locale = 'en'
|
|
# Normalize to short code for comparisons (e.g., 'en' from 'en_US')
|
|
short_locale = (current_locale.split('_', 1)[0] if current_locale else 'en')
|
|
available_languages = current_app.config.get('LANGUAGES', {}) or {}
|
|
current_language_label = available_languages.get(short_locale, short_locale.upper())
|
|
|
|
# Check if current language is RTL
|
|
rtl_languages = current_app.config.get('RTL_LANGUAGES', set())
|
|
is_rtl = short_locale in rtl_languages
|
|
|
|
return {
|
|
'app_name': 'Time Tracker',
|
|
'app_version': version_value,
|
|
'timezone': timezone_name,
|
|
'timezone_offset': get_timezone_offset_for_timezone(timezone_name),
|
|
'current_locale': current_locale,
|
|
'current_language_code': short_locale,
|
|
'current_language_label': current_language_label,
|
|
'is_rtl': is_rtl,
|
|
'available_languages': available_languages,
|
|
'config': current_app.config
|
|
}
|
|
|
|
@app.before_request
|
|
def before_request():
|
|
"""Set up request-specific data"""
|
|
g.request_start_time = request.start_time if hasattr(request, 'start_time') else None
|