Internationalization and Localization

Locale Middleware

Django's locale middleware automatically detects and activates the appropriate language for each request, providing seamless internationalization without requiring manual language management. This chapter covers configuring, customizing, and optimizing locale middleware for sophisticated multilingual applications with advanced language detection and user preference management.

Locale Middleware

Django's locale middleware automatically detects and activates the appropriate language for each request, providing seamless internationalization without requiring manual language management. This chapter covers configuring, customizing, and optimizing locale middleware for sophisticated multilingual applications with advanced language detection and user preference management.

Understanding Locale Middleware

How Locale Middleware Works

Django's LocaleMiddleware follows a specific order to determine the appropriate language:

  1. URL Language Prefix: Check for language code in URL path (/es/blog/)
  2. Session Language: Look for language preference in user session
  3. Cookie Language: Check for language preference in cookies
  4. Accept-Language Header: Parse browser's language preferences
  5. Default Language: Fall back to LANGUAGE_CODE setting

Basic Middleware Configuration

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',  # Add after SessionMiddleware
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Language configuration
LANGUAGE_CODE = 'en'
LANGUAGES = [
    ('en', 'English'),
    ('es', 'Español'),
    ('fr', 'Français'),
    ('de', 'Deutsch'),
    ('ja', '日本語'),
]

# Language cookie settings
LANGUAGE_COOKIE_NAME = 'django_language'
LANGUAGE_COOKIE_AGE = 365 * 24 * 60 * 60  # 1 year
LANGUAGE_COOKIE_DOMAIN = None
LANGUAGE_COOKIE_PATH = '/'
LANGUAGE_COOKIE_SECURE = False  # Set to True in production with HTTPS
LANGUAGE_COOKIE_HTTPONLY = False
LANGUAGE_COOKIE_SAMESITE = 'Lax'

# Session language key
LANGUAGE_SESSION_KEY = 'django_language'

Custom Locale Middleware

Enhanced Locale Middleware

# middleware/locale.py
from django.middleware.locale import LocaleMiddleware
from django.utils import translation
from django.conf import settings
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
import re
import logging

logger = logging.getLogger(__name__)

class EnhancedLocaleMiddleware(LocaleMiddleware):
    """Enhanced locale middleware with additional features."""
    
    def __init__(self, get_response):
        super().__init__(get_response)
        self.get_response = get_response
    
    def process_request(self, request):
        """Process request with enhanced language detection."""
        # Store original language for comparison
        original_language = translation.get_language()
        
        # Custom language detection
        language = self.get_language_from_request(request)
        
        # Activate the detected language
        translation.activate(language)
        request.LANGUAGE_CODE = translation.get_language()
        
        # Log language detection for debugging
        if settings.DEBUG:
            logger.debug(f'Language detected: {language} for {request.path}')
        
        # Store language change for analytics
        if hasattr(request, 'user') and request.user.is_authenticated:
            self.track_language_usage(request, language)
    
    def process_response(self, request, response):
        """Process response with language-specific optimizations."""
        language = getattr(request, 'LANGUAGE_CODE', None)
        
        if language:
            # Set language cookie if changed
            current_cookie_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
            if current_cookie_lang != language:
                response.set_cookie(
                    settings.LANGUAGE_COOKIE_NAME,
                    language,
                    max_age=settings.LANGUAGE_COOKIE_AGE,
                    path=settings.LANGUAGE_COOKIE_PATH,
                    domain=settings.LANGUAGE_COOKIE_DOMAIN,
                    secure=settings.LANGUAGE_COOKIE_SECURE,
                    httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
                    samesite=settings.LANGUAGE_COOKIE_SAMESITE,
                )
            
            # Add language to response headers
            response['Content-Language'] = language
            
            # Add Vary header for caching
            patch_vary_headers(response, ('Accept-Language', 'Cookie'))
        
        return response
    
    def get_language_from_request(self, request):
        """Enhanced language detection with custom logic."""
        # 1. Check URL language prefix (handled by parent class)
        language = super().get_language_from_request(request)
        if language:
            return language
        
        # 2. Check user profile language preference
        if hasattr(request, 'user') and request.user.is_authenticated:
            try:
                profile_language = request.user.userprofile.language
                if profile_language and self.is_language_supported(profile_language):
                    return profile_language
            except AttributeError:
                pass
        
        # 3. Check subdomain-based language
        subdomain_language = self.get_language_from_subdomain(request)
        if subdomain_language:
            return subdomain_language
        
        # 4. Check custom header (for API clients)
        header_language = request.META.get('HTTP_X_LANGUAGE')
        if header_language and self.is_language_supported(header_language):
            return header_language
        
        # 5. Check GeoIP-based language detection
        geoip_language = self.get_language_from_geoip(request)
        if geoip_language:
            return geoip_language
        
        # 6. Fall back to default detection
        return translation.get_language_from_request(request, check_path=True)
    
    def get_language_from_subdomain(self, request):
        """Extract language from subdomain."""
        host = request.get_host().split(':')[0]  # Remove port
        parts = host.split('.')
        
        if len(parts) > 2:  # Has subdomain
            subdomain = parts[0]
            if self.is_language_supported(subdomain):
                return subdomain
        
        return None
    
    def get_language_from_geoip(self, request):
        """Get language based on user's geographic location."""
        try:
            from django.contrib.gis.geoip2 import GeoIP2
            
            # Get client IP
            ip = self.get_client_ip(request)
            if not ip:
                return None
            
            # Get country from IP
            g = GeoIP2()
            country = g.country_code(ip)
            
            # Map countries to languages
            country_language_map = {
                'ES': 'es',  # Spain -> Spanish
                'MX': 'es',  # Mexico -> Spanish
                'AR': 'es',  # Argentina -> Spanish
                'FR': 'fr',  # France -> French
                'CA': 'fr',  # Canada -> French (could be 'en' too)
                'DE': 'de',  # Germany -> German
                'AT': 'de',  # Austria -> German
                'JP': 'ja',  # Japan -> Japanese
                'CN': 'zh-hans',  # China -> Simplified Chinese
                'TW': 'zh-hant',  # Taiwan -> Traditional Chinese
            }
            
            language = country_language_map.get(country)
            if language and self.is_language_supported(language):
                return language
        
        except Exception as e:
            logger.warning(f'GeoIP language detection failed: {e}')
        
        return None
    
    def get_client_ip(self, request):
        """Get client IP address."""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0]
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip
    
    def is_language_supported(self, language_code):
        """Check if language is supported."""
        supported_languages = [lang[0] for lang in settings.LANGUAGES]
        return language_code in supported_languages
    
    def track_language_usage(self, request, language):
        """Track language usage for analytics."""
        # This could be implemented to track language preferences
        # for analytics or user behavior analysis
        pass

class APILocaleMiddleware(MiddlewareMixin):
    """Specialized locale middleware for API endpoints."""
    
    def process_request(self, request):
        """Process API requests with language detection."""
        # Only process API requests
        if not request.path.startswith('/api/'):
            return
        
        # Get language from various sources
        language = (
            request.META.get('HTTP_ACCEPT_LANGUAGE_OVERRIDE') or
            request.GET.get('lang') or
            request.META.get('HTTP_ACCEPT_LANGUAGE', '').split(',')[0].split('-')[0] or
            settings.LANGUAGE_CODE
        )
        
        # Validate and activate language
        if language in [lang[0] for lang in settings.LANGUAGES]:
            translation.activate(language)
            request.LANGUAGE_CODE = language
        else:
            translation.activate(settings.LANGUAGE_CODE)
            request.LANGUAGE_CODE = settings.LANGUAGE_CODE
    
    def process_response(self, request, response):
        """Add language headers to API responses."""
        if hasattr(request, 'LANGUAGE_CODE'):
            response['Content-Language'] = request.LANGUAGE_CODE
        return response

Language Detection Strategies

User Preference Management

# models.py
from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.translation import gettext_lazy as _

class UserLanguagePreference(models.Model):
    """Track user language preferences and history."""
    user = models.OneToOneField(
        User, 
        on_delete=models.CASCADE,
        related_name='language_preference'
    )
    primary_language = models.CharField(
        max_length=10,
        choices=settings.LANGUAGES,
        default=settings.LANGUAGE_CODE,
        verbose_name=_('Primary Language')
    )
    secondary_languages = models.JSONField(
        default=list,
        blank=True,
        verbose_name=_('Secondary Languages'),
        help_text=_('Additional languages the user understands')
    )
    auto_detect = models.BooleanField(
        default=True,
        verbose_name=_('Auto-detect language'),
        help_text=_('Automatically detect language from browser')
    )
    last_detected_language = models.CharField(
        max_length=10,
        blank=True,
        verbose_name=_('Last Detected Language')
    )
    detection_source = models.CharField(
        max_length=20,
        choices=[
            ('manual', _('Manual Selection')),
            ('browser', _('Browser Detection')),
            ('geoip', _('Geographic Location')),
            ('subdomain', _('Subdomain')),
            ('profile', _('User Profile')),
        ],
        blank=True,
        verbose_name=_('Detection Source')
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = _('User Language Preference')
        verbose_name_plural = _('User Language Preferences')
    
    def get_preferred_language(self, available_languages=None):
        """Get user's preferred language from available options."""
        if available_languages is None:
            available_languages = [lang[0] for lang in settings.LANGUAGES]
        
        # Check primary language
        if self.primary_language in available_languages:
            return self.primary_language
        
        # Check secondary languages
        for lang in self.secondary_languages:
            if lang in available_languages:
                return lang
        
        # Fall back to default
        return settings.LANGUAGE_CODE

class LanguageUsageLog(models.Model):
    """Log language usage for analytics."""
    user = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        null=True,
        blank=True
    )
    session_key = models.CharField(max_length=40, blank=True)
    language_code = models.CharField(max_length=10)
    detection_method = models.CharField(
        max_length=20,
        choices=[
            ('url', 'URL Prefix'),
            ('session', 'Session'),
            ('cookie', 'Cookie'),
            ('header', 'Accept-Language Header'),
            ('profile', 'User Profile'),
            ('geoip', 'GeoIP'),
            ('subdomain', 'Subdomain'),
            ('default', 'Default'),
        ]
    )
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    user_agent = models.TextField(blank=True)
    path = models.CharField(max_length=500)
    timestamp = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = _('Language Usage Log')
        verbose_name_plural = _('Language Usage Logs')
        indexes = [
            models.Index(fields=['timestamp', 'language_code']),
            models.Index(fields=['user', 'timestamp']),
        ]

Smart Language Detection

# utils/language_detection.py
from django.conf import settings
from django.utils import translation
import re
from collections import Counter

class SmartLanguageDetector:
    """Advanced language detection with machine learning capabilities."""
    
    def __init__(self):
        self.language_patterns = self.build_language_patterns()
    
    def build_language_patterns(self):
        """Build patterns for language detection from content."""
        return {
            'en': [
                r'\b(the|and|or|but|in|on|at|to|for|of|with|by)\b',
                r'\b(this|that|these|those|here|there|where|when|what|how)\b',
            ],
            'es': [
                r'\b(el|la|los|las|un|una|y|o|pero|en|de|con|por|para)\b',
                r'\b(este|esta|estos|estas|aquí|allí|donde|cuando|qué|cómo)\b',
            ],
            'fr': [
                r'\b(le|la|les|un|une|et|ou|mais|dans|de|avec|par|pour)\b',
                r'\b(ce|cette|ces|ici|||quand|que|comment)\b',
            ],
            'de': [
                r'\b(der|die|das|ein|eine|und|oder|aber|in|von|mit|für)\b',
                r'\b(dieser|diese|dieses|hier|dort|wo|wann|was|wie)\b',
            ],
        }
    
    def detect_from_content(self, text):
        """Detect language from text content."""
        if not text:
            return None
        
        text = text.lower()
        scores = {}
        
        for lang, patterns in self.language_patterns.items():
            score = 0
            for pattern in patterns:
                matches = re.findall(pattern, text, re.IGNORECASE)
                score += len(matches)
            scores[lang] = score
        
        if scores:
            return max(scores, key=scores.get)
        
        return None
    
    def detect_from_user_behavior(self, user):
        """Detect language from user's historical behavior."""
        if not user.is_authenticated:
            return None
        
        try:
            # Get user's recent language usage
            from .models import LanguageUsageLog
            recent_logs = LanguageUsageLog.objects.filter(
                user=user
            ).order_by('-timestamp')[:50]
            
            if recent_logs:
                languages = [log.language_code for log in recent_logs]
                most_common = Counter(languages).most_common(1)
                return most_common[0][0] if most_common else None
        
        except Exception:
            pass
        
        return None
    
    def detect_from_browser_settings(self, request):
        """Enhanced browser language detection."""
        accept_language = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
        
        if not accept_language:
            return None
        
        # Parse Accept-Language header
        languages = []
        for item in accept_language.split(','):
            if ';' in item:
                lang, quality = item.split(';', 1)
                try:
                    q = float(quality.split('=')[1])
                except (IndexError, ValueError):
                    q = 1.0
            else:
                lang, q = item, 1.0
            
            lang = lang.strip().lower()
            
            # Handle language variants (e.g., en-US -> en)
            if '-' in lang:
                lang = lang.split('-')[0]
            
            # Check if language is supported
            if lang in [l[0] for l in settings.LANGUAGES]:
                languages.append((lang, q))
        
        # Sort by quality and return best match
        if languages:
            languages.sort(key=lambda x: x[1], reverse=True)
            return languages[0][0]
        
        return None

detector = SmartLanguageDetector()

Language Switching

Language Switching Views

# views.py
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import translation
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from django.conf import settings
import json

@require_POST
def set_language(request):
    """Set user's language preference."""
    language = request.POST.get('language')
    next_url = request.POST.get('next', request.META.get('HTTP_REFERER', '/'))
    
    if language and language in [lang[0] for lang in settings.LANGUAGES]:
        # Activate language
        translation.activate(language)
        
        # Store in session
        request.session[settings.LANGUAGE_SESSION_KEY] = language
        
        # Store in user profile if authenticated
        if request.user.is_authenticated:
            try:
                preference, created = UserLanguagePreference.objects.get_or_create(
                    user=request.user
                )
                preference.primary_language = language
                preference.detection_source = 'manual'
                preference.save()
            except Exception:
                pass
        
        # Log language change
        log_language_usage(request, language, 'manual')
    
    # Redirect to next URL
    response = redirect(next_url)
    
    # Set language cookie
    if language:
        response.set_cookie(
            settings.LANGUAGE_COOKIE_NAME,
            language,
            max_age=settings.LANGUAGE_COOKIE_AGE,
            path=settings.LANGUAGE_COOKIE_PATH,
            secure=settings.LANGUAGE_COOKIE_SECURE,
            httponly=settings.LANGUAGE_COOKIE_HTTPONLY,
            samesite=settings.LANGUAGE_COOKIE_SAMESITE,
        )
    
    return response

@csrf_exempt
def api_set_language(request):
    """API endpoint for setting language."""
    if request.method != 'POST':
        return JsonResponse({'error': 'Method not allowed'}, status=405)
    
    try:
        data = json.loads(request.body)
        language = data.get('language')
    except (json.JSONDecodeError, AttributeError):
        language = request.POST.get('language')
    
    if not language:
        return JsonResponse({'error': 'Language not specified'}, status=400)
    
    if language not in [lang[0] for lang in settings.LANGUAGES]:
        return JsonResponse({'error': 'Language not supported'}, status=400)
    
    # Set language in session
    request.session[settings.LANGUAGE_SESSION_KEY] = language
    
    # Update user profile if authenticated
    if request.user.is_authenticated:
        try:
            preference, created = UserLanguagePreference.objects.get_or_create(
                user=request.user
            )
            preference.primary_language = language
            preference.detection_source = 'manual'
            preference.save()
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)
    
    # Log language change
    log_language_usage(request, language, 'api')
    
    return JsonResponse({
        'success': True,
        'language': language,
        'message': f'Language set to {language}'
    })

@login_required
def language_preferences(request):
    """Manage user language preferences."""
    try:
        preference = request.user.language_preference
    except UserLanguagePreference.DoesNotExist:
        preference = UserLanguagePreference.objects.create(user=request.user)
    
    if request.method == 'POST':
        primary_language = request.POST.get('primary_language')
        secondary_languages = request.POST.getlist('secondary_languages')
        auto_detect = request.POST.get('auto_detect') == 'on'
        
        if primary_language in [lang[0] for lang in settings.LANGUAGES]:
            preference.primary_language = primary_language
            preference.secondary_languages = secondary_languages
            preference.auto_detect = auto_detect
            preference.save()
            
            # Activate new language
            translation.activate(primary_language)
            request.session[settings.LANGUAGE_SESSION_KEY] = primary_language
            
            return redirect('language_preferences')
    
    context = {
        'preference': preference,
        'available_languages': settings.LANGUAGES,
    }
    
    return render(request, 'accounts/language_preferences.html', context)

def log_language_usage(request, language, method):
    """Log language usage for analytics."""
    try:
        from .models import LanguageUsageLog
        
        LanguageUsageLog.objects.create(
            user=request.user if request.user.is_authenticated else None,
            session_key=request.session.session_key,
            language_code=language,
            detection_method=method,
            ip_address=get_client_ip(request),
            user_agent=request.META.get('HTTP_USER_AGENT', ''),
            path=request.path,
        )
    except Exception:
        # Don't let logging errors break the application
        pass

def get_client_ip(request):
    """Get client IP address."""
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

Template Integration

Language Switching Templates

<!-- templates/includes/language_switcher.html -->
{% load i18n %}

<div class="language-switcher">
    <div class="current-language">
        <span class="language-icon">🌐</span>
        <span class="language-name">{{ LANGUAGE_CODE|upper }}</span>
        <span class="dropdown-arrow"></span>
    </div>
    
    <div class="language-dropdown">
        {% get_available_languages as LANGUAGES %}
        {% get_current_language as LANGUAGE_CODE %}
        
        <form action="{% url 'set_language' %}" method="post" class="language-form">
            {% csrf_token %}
            <input name="next" type="hidden" value="{{ request.get_full_path }}" />
            
            {% for language_code, language_name in LANGUAGES %}
                <button 
                    type="submit" 
                    name="language" 
                    value="{{ language_code }}"
                    class="language-option {% if language_code == LANGUAGE_CODE %}active{% endif %}"
                    {% if language_code == LANGUAGE_CODE %}disabled{% endif %}
                >
                    <span class="language-code">{{ language_code|upper }}</span>
                    <span class="language-name">{{ language_name }}</span>
                    {% if language_code == LANGUAGE_CODE %}
                        <span class="current-indicator"></span>
                    {% endif %}
                </button>
            {% endfor %}
        </form>
    </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const switcher = document.querySelector('.language-switcher');
    const dropdown = switcher.querySelector('.language-dropdown');
    const currentLang = switcher.querySelector('.current-language');
    
    // Toggle dropdown
    currentLang.addEventListener('click', function() {
        dropdown.classList.toggle('show');
    });
    
    // Close dropdown when clicking outside
    document.addEventListener('click', function(event) {
        if (!switcher.contains(event.target)) {
            dropdown.classList.remove('show');
        }
    });
    
    // Handle language selection via AJAX
    const form = switcher.querySelector('.language-form');
    const buttons = form.querySelectorAll('.language-option');
    
    buttons.forEach(button => {
        button.addEventListener('click', function(e) {
            e.preventDefault();
            
            const language = this.value;
            const formData = new FormData();
            formData.append('language', language);
            formData.append('next', window.location.pathname);
            formData.append('csrfmiddlewaretoken', form.querySelector('[name=csrfmiddlewaretoken]').value);
            
            fetch('{% url "api_set_language" %}', {
                method: 'POST',
                body: formData,
                headers: {
                    'X-Requested-With': 'XMLHttpRequest'
                }
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    // Reload page to apply new language
                    window.location.reload();
                } else {
                    console.error('Language change failed:', data.error);
                }
            })
            .catch(error => {
                console.error('Error:', error);
                // Fall back to form submission
                form.submit();
            });
        });
    });
});
</script>

<style>
.language-switcher {
    position: relative;
    display: inline-block;
}

.current-language {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    border-radius: 0.375rem;
    cursor: pointer;
    user-select: none;
}

.current-language:hover {
    background: #e9ecef;
}

.language-dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: white;
    border: 1px solid #dee2e6;
    border-radius: 0.375rem;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    z-index: 1000;
    display: none;
}

.language-dropdown.show {
    display: block;
}

.language-form {
    margin: 0;
}

.language-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 0.75rem 1rem;
    border: none;
    background: none;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out;
}

.language-option:hover:not(:disabled) {
    background: #f8f9fa;
}

.language-option.active {
    background: #e7f3ff;
    font-weight: 600;
}

.language-option:disabled {
    cursor: default;
}

.current-indicator {
    color: #0d6efd;
    font-weight: bold;
}
</style>

Language Preferences Template

<!-- templates/accounts/language_preferences.html -->
{% extends 'base.html' %}
{% load i18n %}

{% block title %}{% trans "Language Preferences" %}{% endblock %}

{% block content %}
<div class="language-preferences">
    <h1>{% trans "Language Preferences" %}</h1>
    
    <form method="post" class="preferences-form">
        {% csrf_token %}
        
        <div class="form-group">
            <label for="primary_language">{% trans "Primary Language" %}</label>
            <select name="primary_language" id="primary_language" class="form-control">
                {% for language_code, language_name in available_languages %}
                    <option 
                        value="{{ language_code }}"
                        {% if language_code == preference.primary_language %}selected{% endif %}
                    >
                        {{ language_name }}
                    </option>
                {% endfor %}
            </select>
            <small class="form-text">{% trans "Your preferred language for the interface" %}</small>
        </div>
        
        <div class="form-group">
            <label>{% trans "Secondary Languages" %}</label>
            <div class="checkbox-group">
                {% for language_code, language_name in available_languages %}
                    <div class="form-check">
                        <input 
                            type="checkbox" 
                            name="secondary_languages" 
                            value="{{ language_code }}"
                            id="secondary_{{ language_code }}"
                            class="form-check-input"
                            {% if language_code in preference.secondary_languages %}checked{% endif %}
                        >
                        <label for="secondary_{{ language_code }}" class="form-check-label">
                            {{ language_name }}
                        </label>
                    </div>
                {% endfor %}
            </div>
            <small class="form-text">{% trans "Languages you understand (used for fallback content)" %}</small>
        </div>
        
        <div class="form-group">
            <div class="form-check">
                <input 
                    type="checkbox" 
                    name="auto_detect" 
                    id="auto_detect"
                    class="form-check-input"
                    {% if preference.auto_detect %}checked{% endif %}
                >
                <label for="auto_detect" class="form-check-label">
                    {% trans "Auto-detect language from browser" %}
                </label>
            </div>
            <small class="form-text">
                {% trans "Automatically detect your preferred language from browser settings" %}
            </small>
        </div>
        
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">
                {% trans "Save Preferences" %}
            </button>
            <a href="{% url 'profile' %}" class="btn btn-secondary">
                {% trans "Cancel" %}
            </a>
        </div>
    </form>
    
    <div class="language-info">
        <h3>{% trans "Current Language Information" %}</h3>
        <dl class="info-list">
            <dt>{% trans "Active Language" %}</dt>
            <dd>{{ LANGUAGE_CODE|upper }} - {% get_language_info for LANGUAGE_CODE as lang_info %}{{ lang_info.name_local }}</dd>
            
            <dt>{% trans "Last Detection Source" %}</dt>
            <dd>{{ preference.get_detection_source_display|default:"—" }}</dd>
            
            <dt>{% trans "Last Detected Language" %}</dt>
            <dd>{{ preference.last_detected_language|upper|default:"—" }}</dd>
            
            <dt>{% trans "Browser Languages" %}</dt>
            <dd id="browser-languages">{% trans "Detecting..." %}</dd>
        </dl>
    </div>
</div>

<script>
// Display browser language preferences
document.addEventListener('DOMContentLoaded', function() {
    const browserLangs = navigator.languages || [navigator.language];
    const browserLangsElement = document.getElementById('browser-languages');
    
    if (browserLangs.length > 0) {
        browserLangsElement.textContent = browserLangs.join(', ');
    } else {
        browserLangsElement.textContent = '{% trans "Not available" %}';
    }
    
    // Handle primary language change
    const primarySelect = document.getElementById('primary_language');
    const secondaryCheckboxes = document.querySelectorAll('input[name="secondary_languages"]');
    
    primarySelect.addEventListener('change', function() {
        const selectedPrimary = this.value;
        
        // Uncheck the primary language from secondary languages
        secondaryCheckboxes.forEach(checkbox => {
            if (checkbox.value === selectedPrimary) {
                checkbox.checked = false;
                checkbox.disabled = true;
            } else {
                checkbox.disabled = false;
            }
        });
    });
    
    // Trigger change event on load
    primarySelect.dispatchEvent(new Event('change'));
});
</script>
{% endblock %}

URL Configuration

Language-Aware URLs

# urls.py
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include
from django.views.i18n import set_language
from . import views

# Non-translatable URLs
urlpatterns = [
    # Language switching
    path('set-language/', set_language, name='set_language'),
    path('api/set-language/', views.api_set_language, name='api_set_language'),
    
    # API endpoints (no language prefix)
    path('api/', include('api.urls')),
    
    # Health checks and admin
    path('health/', include('health.urls')),
    path('admin/', admin.site.urls),
]

# Translatable URLs with language prefix
urlpatterns += i18n_patterns(
    path('', include('blog.urls')),
    path('accounts/', include('accounts.urls')),
    path('preferences/language/', views.language_preferences, name='language_preferences'),
    
    # Prefix default language in URLs (optional)
    prefix_default_language=False,
)

# Custom 404 handler for language-aware URLs
handler404 = 'myapp.views.custom_404'

Language-Specific URL Patterns

# blog/urls.py
from django.urls import path
from django.utils.translation import gettext_lazy as _
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.PostListView.as_view(), name='post_list'),
    
    # Translatable URL patterns
    path(_('posts/'), views.PostListView.as_view(), name='posts'),
    path(_('post/<slug:slug>/'), views.PostDetailView.as_view(), name='post_detail'),
    path(_('category/<slug:slug>/'), views.CategoryView.as_view(), name='category'),
    path(_('tag/<slug:slug>/'), views.TagView.as_view(), name='tag'),
    path(_('archive/<int:year>/'), views.YearArchiveView.as_view(), name='year_archive'),
    path(_('archive/<int:year>/<int:month>/'), views.MonthArchiveView.as_view(), name='month_archive'),
    path(_('search/'), views.SearchView.as_view(), name='search'),
    path(_('feed/'), views.PostFeedView.as_view(), name='feed'),
]

Performance Optimization

Caching with Language Support

# utils/cache.py
from django.core.cache import cache
from django.utils import translation
from django.conf import settings
import hashlib

def get_language_cache_key(base_key, language=None):
    """Generate cache key with language suffix."""
    if language is None:
        language = translation.get_language()
    
    return f"{base_key}_{language}"

def cache_per_language(timeout=3600):
    """Decorator to cache function results per language."""
    def decorator(func):
        def wrapper(*args, **kwargs):
            # Generate cache key
            key_parts = [func.__name__]
            key_parts.extend(str(arg) for arg in args)
            key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items()))
            
            base_key = hashlib.md5('_'.join(key_parts).encode()).hexdigest()
            cache_key = get_language_cache_key(base_key)
            
            # Try to get from cache
            result = cache.get(cache_key)
            if result is not None:
                return result
            
            # Execute function and cache result
            result = func(*args, **kwargs)
            cache.set(cache_key, result, timeout)
            
            return result
        return wrapper
    return decorator

# Cache configuration for multilingual content
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'KEY_PREFIX': f'myapp_{settings.LANGUAGE_CODE}',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    },
    'translations': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/2',
        'KEY_PREFIX': 'translations',
        'TIMEOUT': 86400,  # 24 hours
    }
}

Middleware Performance Optimization

# middleware/optimized_locale.py
from django.middleware.locale import LocaleMiddleware
from django.utils import translation
from django.core.cache import cache
from django.conf import settings

class OptimizedLocaleMiddleware(LocaleMiddleware):
    """Optimized locale middleware with caching."""
    
    def __init__(self, get_response):
        super().__init__(get_response)
        self.cache_timeout = getattr(settings, 'LANGUAGE_DETECTION_CACHE_TIMEOUT', 300)
    
    def process_request(self, request):
        """Optimized language detection with caching."""
        # Generate cache key for this request
        cache_key = self.get_cache_key(request)
        
        # Try to get language from cache
        language = cache.get(cache_key)
        
        if language is None:
            # Detect language using parent method
            language = translation.get_language_from_request(request, check_path=True)
            
            # Cache the result
            cache.set(cache_key, language, self.cache_timeout)
        
        # Activate language
        translation.activate(language)
        request.LANGUAGE_CODE = language
    
    def get_cache_key(self, request):
        """Generate cache key for language detection."""
        key_parts = [
            'lang_detect',
            request.path_info,
            request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
            request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, ''),
        ]
        
        # Add user ID if authenticated
        if hasattr(request, 'user') and request.user.is_authenticated:
            key_parts.append(str(request.user.id))
        
        return '_'.join(str(part) for part in key_parts if part)

Testing Locale Middleware

Middleware Tests

# tests/test_locale_middleware.py
from django.test import TestCase, RequestFactory, override_settings
from django.contrib.auth.models import User
from django.utils import translation
from django.conf import settings
from middleware.locale import EnhancedLocaleMiddleware

class LocaleMiddlewareTestCase(TestCase):
    def setUp(self):
        self.factory = RequestFactory()
        self.middleware = EnhancedLocaleMiddleware(lambda r: None)
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass'
        )
    
    def test_url_language_detection(self):
        """Test language detection from URL prefix."""
        request = self.factory.get('/es/blog/')
        request.user = self.user
        
        self.middleware.process_request(request)
        
        self.assertEqual(request.LANGUAGE_CODE, 'es')
    
    def test_user_profile_language(self):
        """Test language detection from user profile."""
        # Set user's preferred language
        profile = self.user.userprofile
        profile.language = 'fr'
        profile.save()
        
        request = self.factory.get('/blog/')
        request.user = self.user
        request.session = {}
        request.COOKIES = {}
        
        self.middleware.process_request(request)
        
        self.assertEqual(request.LANGUAGE_CODE, 'fr')
    
    def test_cookie_language_detection(self):
        """Test language detection from cookie."""
        request = self.factory.get('/blog/')
        request.user = self.user
        request.session = {}
        request.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'de'}
        
        self.middleware.process_request(request)
        
        self.assertEqual(request.LANGUAGE_CODE, 'de')
    
    def test_accept_language_header(self):
        """Test language detection from Accept-Language header."""
        request = self.factory.get('/blog/', HTTP_ACCEPT_LANGUAGE='ja,en;q=0.9')
        request.user = self.user
        request.session = {}
        request.COOKIES = {}
        
        self.middleware.process_request(request)
        
        self.assertEqual(request.LANGUAGE_CODE, 'ja')
    
    def test_subdomain_language_detection(self):
        """Test language detection from subdomain."""
        request = self.factory.get('/blog/', HTTP_HOST='es.example.com')
        request.user = self.user
        request.session = {}
        request.COOKIES = {}
        
        language = self.middleware.get_language_from_subdomain(request)
        
        self.assertEqual(language, 'es')
    
    @override_settings(LANGUAGES=[('en', 'English'), ('es', 'Spanish')])
    def test_unsupported_language_fallback(self):
        """Test fallback to default language for unsupported languages."""
        request = self.factory.get('/blog/', HTTP_ACCEPT_LANGUAGE='zh-cn')
        request.user = self.user
        request.session = {}
        request.COOKIES = {}
        
        self.middleware.process_request(request)
        
        self.assertEqual(request.LANGUAGE_CODE, settings.LANGUAGE_CODE)
    
    def test_language_cookie_setting(self):
        """Test that language cookie is set in response."""
        request = self.factory.get('/blog/')
        request.user = self.user
        request.session = {}
        request.COOKIES = {}
        request.LANGUAGE_CODE = 'es'
        
        from django.http import HttpResponse
        response = HttpResponse()
        
        response = self.middleware.process_response(request, response)
        
        # Check if cookie is set
        self.assertIn(settings.LANGUAGE_COOKIE_NAME, response.cookies)
        self.assertEqual(response.cookies[settings.LANGUAGE_COOKIE_NAME].value, 'es')

Analytics and Monitoring

Language Usage Analytics

# analytics/language_analytics.py
from django.db.models import Count, Q
from django.utils import timezone
from datetime import timedelta
from .models import LanguageUsageLog

class LanguageAnalytics:
    """Analytics for language usage patterns."""
    
    def get_language_distribution(self, days=30):
        """Get language usage distribution over time period."""
        since = timezone.now() - timedelta(days=days)
        
        return LanguageUsageLog.objects.filter(
            timestamp__gte=since
        ).values('language_code').annotate(
            count=Count('id')
        ).order_by('-count')
    
    def get_detection_method_stats(self, days=30):
        """Get statistics on language detection methods."""
        since = timezone.now() - timedelta(days=days)
        
        return LanguageUsageLog.objects.filter(
            timestamp__gte=since
        ).values('detection_method').annotate(
            count=Count('id')
        ).order_by('-count')
    
    def get_user_language_preferences(self):
        """Get user language preference statistics."""
        from .models import UserLanguagePreference
        
        return UserLanguagePreference.objects.values(
            'primary_language'
        ).annotate(
            count=Count('id')
        ).order_by('-count')
    
    def get_geographic_language_distribution(self):
        """Get language distribution by geographic location."""
        # This would require GeoIP integration
        pass
    
    def generate_language_report(self, days=30):
        """Generate comprehensive language usage report."""
        return {
            'language_distribution': self.get_language_distribution(days),
            'detection_methods': self.get_detection_method_stats(days),
            'user_preferences': self.get_user_language_preferences(),
            'total_requests': LanguageUsageLog.objects.filter(
                timestamp__gte=timezone.now() - timedelta(days=days)
            ).count(),
            'unique_users': LanguageUsageLog.objects.filter(
                timestamp__gte=timezone.now() - timedelta(days=days),
                user__isnull=False
            ).values('user').distinct().count(),
        }

analytics = LanguageAnalytics()

Django's locale middleware provides the foundation for sophisticated multilingual applications. By customizing language detection logic, implementing user preferences, and optimizing performance through caching, you can create applications that automatically adapt to users' language preferences while maintaining excellent performance. The key is balancing automatic detection with user control, ensuring that language switching is both seamless and predictable.