CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-rosetta

A Django application that facilitates the translation process of your Django projects

Pending
Overview
Eval results
Files

django-signals.mddocs/

Django Signals

Event system for extending Rosetta functionality and integrating with custom workflows through Django's signal framework. These signals provide hooks for monitoring translation activities, implementing custom business logic, and integrating with external systems.

Capabilities

Translation Entry Change Signal

Signal fired when individual translation entries are modified, providing detailed information about the change.

entry_changed = django.dispatch.Signal()
"""
Signal fired when translation entry is modified.

Sent whenever a translation entry (msgstr) is changed in the web interface,
allowing custom code to respond to translation modifications.

Signal Arguments:
- sender: The view class that triggered the change
- user: Django User instance who made the change
- old_msgstr: Previous translation string value
- old_fuzzy: Previous fuzzy flag state (boolean)
- pofile: POFile object representing the .po file
- language_code: Language code of the modified translation
- request: Django HttpRequest instance

Usage:
@receiver(entry_changed)
def handle_entry_changed(sender, **kwargs):
    user = kwargs['user']
    old_msgstr = kwargs['old_msgstr']
    language_code = kwargs['language_code']
    # Custom logic here
"""

@receiver(entry_changed)
def log_translation_changes(sender, **kwargs):
    """
    Example signal handler for logging translation changes.
    
    Parameters from signal:
    - sender: View class
    - user: User who made the change
    - old_msgstr: Previous translation
    - old_fuzzy: Previous fuzzy state
    - pofile: POFile object
    - language_code: Language being translated
    - request: HTTP request
    """

Post-Save Signal

Signal fired after .po files are saved to disk, enabling post-processing workflows.

post_save = django.dispatch.Signal()
"""
Signal fired after .po file is saved to disk.

Sent after translation changes are committed to the .po file, allowing
custom code to perform post-processing, notifications, or integrations.

Signal Arguments:
- sender: The view class that saved the file
- language_code: Language code of the saved file
- request: Django HttpRequest instance

Usage:
@receiver(post_save)
def handle_po_file_saved(sender, **kwargs):
    language_code = kwargs['language_code']
    request = kwargs['request']
    # Custom post-save logic here
"""

@receiver(post_save)
def compile_mo_files(sender, **kwargs):
    """
    Example signal handler for compiling .mo files after .po save.
    
    Parameters from signal:
    - sender: View class
    - language_code: Language that was saved
    - request: HTTP request
    """

Usage Examples

Basic Signal Handlers

# myapp/signals.py
from django.dispatch import receiver
from rosetta.signals import entry_changed, post_save
import logging

logger = logging.getLogger('translation_activity')

@receiver(entry_changed)
def log_translation_activity(sender, **kwargs):
    """Log all translation changes for audit purposes."""
    
    user = kwargs['user']
    old_msgstr = kwargs['old_msgstr']
    language_code = kwargs['language_code']
    pofile = kwargs['pofile']
    
    # Find the changed entry (simplified example)
    for entry in pofile:
        if entry.msgstr != old_msgstr and entry.msgstr:  # New translation
            logger.info(
                f"Translation changed by {user.username}: "
                f"'{entry.msgid}' -> '{entry.msgstr}' ({language_code})"
            )
            break

@receiver(post_save)
def notify_translation_team(sender, **kwargs):
    """Send notifications when translations are saved."""
    
    language_code = kwargs['language_code']
    request = kwargs['request']
    
    # Send email notification (example)
    from django.core.mail import send_mail
    
    send_mail(
        subject=f'Translations updated for {language_code}',
        message=f'User {request.user.username} has updated {language_code} translations.',
        from_email='noreply@example.com',
        recipient_list=['translation-team@example.com'],
        fail_silently=True
    )

Integration with Version Control

# myapp/signals.py
from django.dispatch import receiver
from rosetta.signals import post_save
import subprocess
import os

@receiver(post_save)
def auto_commit_translations(sender, **kwargs):
    """Automatically commit translation changes to git."""
    
    language_code = kwargs['language_code']
    request = kwargs['request']
    
    try:
        # Navigate to project root
        project_root = '/path/to/your/project'
        os.chdir(project_root)
        
        # Add all .po files
        subprocess.run(['git', 'add', '*.po'], check=True)
        
        # Commit with descriptive message
        commit_message = f"Update {language_code} translations by {request.user.username}"
        subprocess.run(['git', 'commit', '-m', commit_message], check=True)
        
        logger.info(f"Auto-committed {language_code} translations")
        
    except subprocess.CalledProcessError as e:
        logger.error(f"Failed to auto-commit translations: {e}")

@receiver(entry_changed)
def track_translation_quality(sender, **kwargs):
    """Track translation quality metrics."""
    
    user = kwargs['user']
    old_msgstr = kwargs['old_msgstr']
    old_fuzzy = kwargs['old_fuzzy']
    pofile = kwargs['pofile']
    
    # Find the changed entry
    for entry in pofile:
        if hasattr(entry, 'msgstr') and entry.msgstr != old_msgstr:
            # Track quality changes
            if old_fuzzy and not entry.fuzzy:
                # Fuzzy entry was clarified
                record_quality_improvement(user, entry, 'fuzzy_resolved')
            elif not old_msgstr and entry.msgstr:
                # New translation added
                record_quality_improvement(user, entry, 'translation_added')
            elif old_msgstr and not entry.msgstr:
                # Translation removed
                record_quality_decline(user, entry, 'translation_removed')
            break

def record_quality_improvement(user, entry, action_type):
    """Record translation quality improvements."""
    # Implementation depends on your quality tracking system
    pass

def record_quality_decline(user, entry, action_type):
    """Record translation quality declines."""
    # Implementation depends on your quality tracking system
    pass

Custom Workflow Integration

# myapp/signals.py
from django.dispatch import receiver
from rosetta.signals import entry_changed, post_save
from myapp.models import TranslationWorkflow, TranslationReview

@receiver(entry_changed)
def trigger_review_workflow(sender, **kwargs):
    """Trigger review workflow for translation changes."""
    
    user = kwargs['user']
    language_code = kwargs['language_code']
    pofile = kwargs['pofile']
    
    # Create review request for significant changes
    workflow, created = TranslationWorkflow.objects.get_or_create(
        language_code=language_code,
        status='in_progress'
    )
    
    # Add review item
    TranslationReview.objects.create(
        workflow=workflow,
        translator=user,
        po_file_path=pofile.fpath,
        status='pending_review'
    )

@receiver(post_save)
def update_translation_cache(sender, **kwargs):
    """Update cached translation statistics."""
    
    language_code = kwargs['language_code']
    
    # Clear relevant caches
    from django.core.cache import cache
    cache_keys = [
        f'translation_stats_{language_code}',
        f'translation_progress_{language_code}',
        'translation_overview'
    ]
    
    for key in cache_keys:
        cache.delete(key)
    
    # Regenerate statistics asynchronously
    from myapp.tasks import update_translation_statistics
    update_translation_statistics.delay(language_code)

@receiver(post_save)
def backup_translations(sender, **kwargs):
    """Create backup of translation files."""
    
    language_code = kwargs['language_code']
    request = kwargs['request']
    
    from myapp.utils import create_translation_backup
    
    try:
        backup_path = create_translation_backup(language_code)
        logger.info(f"Created translation backup: {backup_path}")
        
        # Optionally store backup metadata
        from myapp.models import TranslationBackup
        TranslationBackup.objects.create(
            language_code=language_code,
            backup_path=backup_path,
            created_by=request.user,
            reason='auto_save'
        )
        
    except Exception as e:
        logger.error(f"Failed to create translation backup: {e}")

Metrics and Analytics

# myapp/signals.py
from django.dispatch import receiver
from rosetta.signals import entry_changed, post_save
from django.utils import timezone
from myapp.models import TranslationMetrics

@receiver(entry_changed)
def track_translation_metrics(sender, **kwargs):
    """Track detailed translation activity metrics."""
    
    user = kwargs['user']
    old_msgstr = kwargs['old_msgstr']
    language_code = kwargs['language_code']
    
    # Determine action type
    if not old_msgstr:
        action_type = 'translation_added'
    elif not kwargs.get('new_msgstr'):  # Would need to get this from entry
        action_type = 'translation_removed'
    else:
        action_type = 'translation_modified'
    
    # Record metrics
    TranslationMetrics.objects.create(
        user=user,
        language_code=language_code,
        action_type=action_type,
        timestamp=timezone.now()
    )

@receiver(post_save)
def update_project_statistics(sender, **kwargs):
    """Update project-wide translation statistics."""
    
    language_code = kwargs['language_code']
    
    # Calculate updated statistics
    from rosetta.poutil import find_pos
    
    files = find_pos(language_code)
    
    total_translated = 0
    total_untranslated = 0
    total_fuzzy = 0
    
    for file_info in files:
        stats = file_info['stats']
        total_translated += stats.get('translated', 0)
        total_untranslated += stats.get('untranslated', 0)
        total_fuzzy += stats.get('fuzzy', 0)
    
    # Store in cache for quick access
    from django.core.cache import cache
    cache.set(f'project_translation_stats_{language_code}', {
        'translated': total_translated,
        'untranslated': total_untranslated,
        'fuzzy': total_fuzzy,
        'completion_percentage': (total_translated / (total_translated + total_untranslated + total_fuzzy)) * 100 if (total_translated + total_untranslated + total_fuzzy) > 0 else 0,
        'last_updated': timezone.now().isoformat()
    }, timeout=3600)  # Cache for 1 hour

Signal Registration

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myapp'
    
    def ready(self):
        # Import signal handlers to register them
        import myapp.signals

# myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'

Testing Signal Handlers

# myapp/tests.py
from django.test import TestCase, RequestFactory
from django.contrib.auth.models import User
from rosetta.signals import entry_changed, post_save
from unittest.mock import Mock, patch

class SignalHandlerTests(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
    
    def test_entry_changed_signal(self):
        """Test entry_changed signal is properly handled."""
        
        request = self.factory.post('/')
        request.user = self.user
        
        # Mock POFile object
        pofile = Mock()
        pofile.fpath = '/path/to/test.po'
        
        # Send signal
        with patch('myapp.signals.logger') as mock_logger:
            entry_changed.send(
                sender=Mock,
                user=self.user,
                old_msgstr='old translation',
                old_fuzzy=True,
                pofile=pofile,
                language_code='fr',
                request=request
            )
            
            # Verify handler was called
            mock_logger.info.assert_called()
    
    def test_post_save_signal(self):
        """Test post_save signal is properly handled."""
        
        request = self.factory.post('/')
        request.user = self.user
        
        # Send signal
        with patch('myapp.signals.send_mail') as mock_send_mail:
            post_save.send(
                sender=Mock,
                language_code='es',
                request=request
            )
            
            # Verify notification was sent
            mock_send_mail.assert_called_once()

Signal Debugging

# myapp/signals.py
import logging
from django.dispatch import receiver
from rosetta.signals import entry_changed, post_save

# Configure detailed logging for signal debugging
signal_logger = logging.getLogger('rosetta.signals')

@receiver(entry_changed)
def debug_entry_changed(sender, **kwargs):
    """Debug handler for entry_changed signal."""
    
    signal_logger.debug(f"entry_changed signal received:")
    signal_logger.debug(f"  sender: {sender}")
    signal_logger.debug(f"  user: {kwargs.get('user')}")
    signal_logger.debug(f"  old_msgstr: {kwargs.get('old_msgstr')}")
    signal_logger.debug(f"  old_fuzzy: {kwargs.get('old_fuzzy')}")
    signal_logger.debug(f"  language_code: {kwargs.get('language_code')}")
    signal_logger.debug(f"  pofile: {kwargs.get('pofile')}")

@receiver(post_save)
def debug_post_save(sender, **kwargs):
    """Debug handler for post_save signal."""
    
    signal_logger.debug(f"post_save signal received:")
    signal_logger.debug(f"  sender: {sender}")
    signal_logger.debug(f"  language_code: {kwargs.get('language_code')}")
    signal_logger.debug(f"  request.user: {kwargs.get('request', {}).user if kwargs.get('request') else 'None'}")

# In settings.py for debugging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'rosetta.signals': {
            'handlers': ['console'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

Install with Tessl CLI

npx tessl i tessl/pypi-django-rosetta

docs

access-control.md

configuration.md

django-signals.md

file-operations.md

index.md

storage-backends.md

template-integration.md

translation-services.md

web-interface.md

tile.json