i18n+filtering fork - fluent-templates v2

smokesignal API Reference#

Template Rendering System#

TemplateRenderer#

The TemplateRenderer is the core component of smokesignal's unified template rendering system. It provides automatic context enrichment with i18n, HTMX, and gender data.

Basic Usage#

use smokesignal::http::template_renderer::TemplateRenderer;
use smokesignal::http::macros::create_renderer;

// Create renderer with all context
let renderer = TemplateRenderer::new(
    template_engine,
    i18n_context,
    language,
    is_htmx
).with_gender_context(gender_context);

// Render template
let html = renderer.render("template_name", &context)?;

Constructor Methods#

new()#
pub fn new(
    template_engine: &'a Environment<'a>,
    i18n_context: &'a I18nTemplateContext,
    language: Language,
    is_htmx: bool,
) -> Self

Creates a new TemplateRenderer with basic context.

Parameters:

  • template_engine: minijinja Environment for template rendering
  • i18n_context: I18n context for translation support
  • language: Current user language (Language wrapper)
  • is_htmx: Whether this is an HTMX request
with_gender_context()#
pub fn with_gender_context(self, gender_context: Option<&'a GenderContext>) -> Self

Adds gender context for personalized content.

Parameters:

  • gender_context: Optional gender context for gendered translations

Rendering Methods#

render()#
pub fn render(&self, template_name: &str, context: &impl Serialize) -> Result<String, TemplateError>

Renders a template with automatic context enrichment.

Parameters:

  • template_name: Name of template (without extension)
  • context: Template context data

Returns: Rendered HTML string or error

render_error()#
pub fn render_error(&self, template_name: &str, context: &impl Serialize, error_context: Value) -> Result<String, TemplateError>

Renders error templates with merged context.

Parameters:

  • template_name: Error template name
  • context: Base template context
  • error_context: Additional error-specific context

Context Enrichment#

The TemplateRenderer automatically adds the following to all template contexts:

// Base context automatically added
{
    "locale": "en-us",                    // Current language
    "is_htmx": false,                     // HTMX request detection
    "gender": "neutral",                  // User gender preference
    "tr": TranslationFunction,            // Translation function
    "current_locale": LocaleFunction,     // Current locale function
    "has_locale": HasLocaleFunction,      // Locale availability check
}

Template Selection#

Templates are automatically selected based on:

  1. Language: template.{locale}.html
  2. Request Type:
    • .partial.html for HTMX requests
    • .bare.html for bare requests
    • .html for full page requests
  3. Fallback: Falls back to English if locale template missing

Template Naming Convention#

templates/
├── home.en-us.html              # Full English template
├── home.en-us.partial.html      # HTMX partial
├── home.en-us.bare.html         # Bare template (no layout)
├── home.fr-ca.html              # Full French template
├── home.fr-ca.partial.html      # HTMX partial (French)
└── error.en-us.html             # Error template

Macros#

create_renderer!#

create_renderer!(template_engine, i18n_context, language, is_htmx, gender_context)

Convenience macro for creating TemplateRenderer instances.

contextual_error!#

contextual_error!(renderer, "error_template", error_context)

Renders error templates with context preservation.

I18n Integration#

I18nTemplateContext#

Provides dynamic locale support for templates.

use smokesignal::i18n::I18nTemplateContext;

// Create context
let i18n_context = I18nTemplateContext::new(loader);

// Use in templates via TemplateRenderer
let renderer = TemplateRenderer::new(engine, &i18n_context, language, is_htmx);

Translation Functions#

Templates have access to several i18n functions:

tr(key, args)#
<!-- Basic translation -->
{{ tr("welcome-message") }}

<!-- Translation with arguments -->
{{ tr("user-greeting", {"name": user.name}) }}

<!-- Gender-aware translation (French) -->
{{ tr("welcome-user", {"name": user.name, "gender": user.gender}) }}
current_locale()#
<!-- Get current locale -->
<html lang="{{ current_locale() }}">
has_locale(locale)#
<!-- Check if locale is available -->
{% if has_locale("fr-ca") %}
  <a href="/fr-ca/">Français</a>
{% endif %}

Language and Locale Management#

Language Wrapper#

The Language type wraps LanguageIdentifier for consistent handling:

use smokesignal::i18n::Language;

// Create from identifier
let language = Language(langid!("en-US"));

// Access identifier
let id = language.0;

Supported Languages#

use smokesignal::i18n::SUPPORTED_LANGUAGES;

// Available languages
const SUPPORTED_LANGUAGES: &[&str] = &["en-us", "fr-ca"];

Language Detection#

use smokesignal::http::middleware_i18n::{
    detect_language_from_headers,
    detect_language_from_cookie,
    language_matches_any
};

// Detect from Accept-Language header
let language = detect_language_from_headers(&headers)?;

// Detect from cookie
let language = detect_language_from_cookie(&cookie_value)?;

// Check if language is supported
let is_supported = language_matches_any(&language);

Error Handling#

TemplateError#

use smokesignal::http::template_renderer::TemplateError;

pub enum TemplateError {
    RenderError(minijinja::Error),
    ContextError(String),
}

Error Context#

Error templates receive merged context:

// Base context + error context
{
    "error_message": "Translation key failed",
    "error_code": "TEMPLATE_001",
    "debug_info": {...},
    // ... plus all base template context
}

HTMX Integration#

HTMX Detection#

use smokesignal::http::middleware_i18n::{HX_REQUEST, HX_TRIGGER};

// Check headers
let is_htmx = headers.get(HX_REQUEST).is_some();
let trigger = headers.get(HX_TRIGGER);

Partial Rendering#

HTMX requests automatically use .partial.html templates:

// Automatically selects:
// - home.en-us.partial.html for HTMX
// - home.en-us.html for full requests
let html = renderer.render("home", &context)?;

Gender Context#

GenderContext#

use smokesignal::i18n::gender::{Gender, GenderContext};

// Create gender context
let gender_context = GenderContext {
    user_gender: Some(Gender::Female),
    target_gender: None,
};

// Use in renderer
let renderer = renderer.with_gender_context(Some(&gender_context));

Gender Values#

pub enum Gender {
    Male,      // "male"
    Female,    // "female"  
    Neutral,   // "neutral"
}

Gender values are automatically available in templates as strings.

Performance Considerations#

Compile-Time Optimizations#

  • Static Translation Loading: All translations compiled into binary
  • Template Validation: Templates validated at compile time
  • Zero Runtime I/O: No file system access for translations

Runtime Optimizations#

  • HTMX Header Caching: Constants for faster header parsing
  • Early Exit Language Detection: Optimized language matching
  • Context Reuse: Efficient context composition

Memory Usage#

  • Shared References: Template engines and i18n contexts are shared
  • Strategic Cloning: Only clone when necessary for API compatibility
  • Minimal Allocations: Efficient string handling in translations

Migration Guide#

From Old System#

The new system is backwards compatible, but for optimal performance:

  1. Replace direct template calls:

    // Old
    template_engine.render("template", &context)?;
    
    // New
    let renderer = create_renderer!(engine, i18n, lang, htmx, gender);
    renderer.render("template", &context)?;
    
  2. Use unified error handling:

    // Old
    if let Err(e) = result {
        return render_error_template(&e);
    }
    
    // New
    contextual_error!(renderer, "error_template", error_context)
    
  3. Leverage automatic context:

    // Old
    let mut context = minijinja::context! { ... };
    context.insert("locale", &locale);
    context.insert("is_htmx", is_htmx);
    
    // New - automatic
    renderer.render("template", &base_context)?;
    

Best Practices#

  1. Use create_renderer! macro for concise renderer creation
  2. Leverage automatic context instead of manual context building
  3. Use contextual_error! for consistent error handling
  4. Test with multiple locales to ensure fallback behavior
  5. Validate gender variants for French Canadian content

Testing#

Unit Tests#

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_template_rendering() {
        let renderer = create_test_renderer();
        let html = renderer.render("test", &context!{}).unwrap();
        assert!(html.contains("expected content"));
    }
}

Integration Tests#

# Test entire i18n system
cargo test i18n

# Test template rendering
cargo test template_renderer

# Test specific language
cargo test i18n -- fr_ca