i18n+filtering fork - fluent-templates v2

Locale Parameters API Documentation#

Overview#

The Smokesignal event filtering system provides comprehensive internationalization (i18n) support through locale-aware API endpoints. This document describes how to use locale parameters in filtering endpoints and configure i18n middleware.

Supported Languages#

  • en-us: English (United States) - Default
  • fr-ca: French (Canada) with gender support

API Endpoints with Locale Support#

Filter Events Endpoint#

URL: GET /events

Locale Detection Priority:

  1. User profile language settings (authenticated users)
  2. language cookie value
  3. Accept-Language HTTP header
  4. Default fallback to en-US

Query Parameters#

All standard filtering parameters are supported with locale-aware responses:

GET /events?mode=inperson&status=scheduled&date_range=today

Headers#

Accept-Language Header#

The system parses standard Accept-Language headers with quality values:

Accept-Language: fr-CA,fr;q=0.9,en;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Language: es-ES,es;q=0.9,en;q=0.8

Examples:

  • fr-CA,fr;q=0.9,en;q=0.8 → Returns French Canadian interface
  • en-US,en;q=0.9 → Returns English interface
  • invalid-locale → Falls back to English
HTMX Language Headers#

For HTMX requests, language context is preserved:

HX-Request: true
HX-Current-Language: fr-CA
Accept-Language: fr-CA,fr;q=0.9,en;q=0.8

Response Format#

The API returns locale-aware responses with:

  • Translated facet labels: Categories, date ranges, status options
  • Localized display names: Event modes, status values
  • Proper pluralization: Event counts, result summaries
  • Currency formatting: Price displays (if applicable)
  • Date formatting: Locale-specific date representations

Example Request/Response#

Request:

GET /events?mode=inperson&date_range=today
Accept-Language: fr-CA,fr;q=0.9,en;q=0.8

Response (French Canadian):

{
  "events": [...],
  "facets": {
    "modes": [
      {
        "value": "inperson",
        "count": 15,
        "i18n_key": "mode-inperson",
        "display_name": "En personne"
      }
    ],
    "date_ranges": [
      {
        "value": "today",
        "count": 8,
        "i18n_key": "date-range-today",
        "display_name": "Aujourd'hui"
      }
    ]
  },
  "total_count": 15,
  "current_locale": "fr-CA"
}

Facets-Only Endpoint#

URL: GET /events/facets

Returns only facet data with translations:

GET /events/facets?mode=online
Accept-Language: fr-CA

Event Suggestions Endpoint#

URL: GET /events/suggestions

Autocomplete suggestions with locale-aware labels:

GET /events/suggestions?q=concert
Accept-Language: fr-CA

Cache Behavior#

The system uses locale-aware caching to ensure optimal performance:

Cache Key Format#

filter:{criteria_hash}:{locale}

Examples:

  • filter:abc123:en-us - English cache entry
  • filter:abc123:fr-ca - French Canadian cache entry

Cache Isolation#

Each locale maintains separate cache entries to prevent:

  • Translation leakage between languages
  • Incorrect facet display names
  • Inconsistent user experiences

Error Handling#

Invalid Locale Fallback#

When an unsupported locale is requested:

  1. Log warning: Invalid locale detected
  2. Fallback: Automatically use en-US
  3. Continue processing: No error returned to client

Malformed Headers#

The system gracefully handles:

  • Empty Accept-Language headers
  • Malformed quality values
  • Invalid language tags
  • Excessively long header values

Error Response Format#

Error messages respect the user's language preference:

{
  "error": "Invalid filter criteria",
  "error_code": "INVALID_CRITERIA",
  "locale": "fr-CA",
  "message": "Critères de filtre invalides"
}

Performance Considerations#

Optimizations#

  • Static Translation Loading: Translations compiled into binary
  • Locale-Specific Caching: Separate cache entries per locale
  • Lazy Loading: Translations loaded on demand
  • Memory Efficiency: Shared FluentBundle resources

Benchmarks#

Typical performance impact of i18n features:

  • Translation lookup: < 1μs
  • Facet calculation with locale: +5-10ms
  • Cache key generation: < 100ns
  • Accept-Language parsing: < 10μs

Integration Examples#

Frontend JavaScript#

// Set user language preference
fetch('/events?mode=online', {
  headers: {
    'Accept-Language': navigator.language || 'en-US'
  }
});

// HTMX with language context
<div hx-get="/events" 
     hx-headers='{"Accept-Language": "fr-CA"}'>
</div>

cURL Examples#

# Request with French Canadian preference
curl -H "Accept-Language: fr-CA,fr;q=0.9,en;q=0.8" \
  "https://api.example.com/events?mode=inperson"

# Request with quality values
curl -H "Accept-Language: en-US,en;q=0.9,fr;q=0.8" \
  "https://api.example.com/events/facets"

# HTMX request with language context
curl -H "HX-Request: true" \
     -H "HX-Current-Language: fr-CA" \
     -H "Accept-Language: fr-CA" \
  "https://api.example.com/events"

Server Integration#

use smokesignal::http::middleware_i18n::Language;
use smokesignal::filtering::FilteringService;

// Handler with automatic locale extraction
pub async fn handle_filter_events(
    ctx: UserRequestContext,
    Extension(filtering_service): Extension<FilteringService>,
    // Language automatically extracted from headers/cookies/profile
    language: Language,
    query: Query<FilterParams>,
) -> Result<Response, WebError> {
    let locale_str = language.to_string();
    
    // Use locale-aware filtering
    let results = filtering_service
        .filter_events_with_locale(&criteria, &options, &locale_str)
        .await?;
    
    // Template rendering with i18n context
    let html = renderer.render("events", &context)?;
    Ok(html)
}

Security Considerations#

Input Validation#

  • Locale strings: Validated against supported languages
  • Header parsing: Protected against injection attacks
  • Cache keys: Sanitized to prevent cache poisoning

Rate Limiting#

Locale-aware rate limiting considers:

  • Language-specific cache effectiveness
  • Regional usage patterns
  • Translation server load

Migration Guide#

Adding New Languages#

  1. Create translation files: Add .ftl files in i18n/{locale}/
  2. Update supported languages: Modify SUPPORTED_LANGUAGES constant
  3. Test translation coverage: Run cargo test i18n
  4. Deploy: Translation files are embedded in binary

Upgrading Existing Code#

// Old: No locale support
let results = filtering_service.filter_events(&criteria).await?;

// New: With locale support
let results = filtering_service
    .filter_events_with_locale(&criteria, &options, &locale)
    .await?;

Troubleshooting#

Common Issues#

Issue: Translations not appearing

  • Check: Translation files in correct directory structure
  • Verify: Locale string format (en-US not en_US)
  • Test: Use browser dev tools to inspect Accept-Language header

Issue: Cache inconsistency between languages

  • Solution: Clear locale-specific cache entries
  • Command: FLUSHDB or restart Redis/Valkey

Issue: Performance degradation

  • Monitor: Cache hit rates per locale
  • Optimize: Translation loading patterns
  • Scale: Consider translation CDN for high-traffic deployments

Debug Information#

Enable debug logging for i18n operations:

RUST_LOG=smokesignal::i18n=debug,smokesignal::filtering=debug cargo run

Log Examples:

DEBUG smokesignal::i18n: Language detected from header: fr-CA
DEBUG smokesignal::filtering: Cache key generated: filter:abc123:fr-ca
DEBUG smokesignal::i18n: Translation lookup: date-range-today -> Aujourd'hui