i18n+filtering fork - fluent-templates v2
at main 397 lines 9.4 kB view raw view rendered
1# smokesignal API Reference 2 3## Template Rendering System 4 5### TemplateRenderer 6 7The `TemplateRenderer` is the core component of smokesignal's unified template rendering system. It provides automatic context enrichment with i18n, HTMX, and gender data. 8 9#### Basic Usage 10 11```rust 12use smokesignal::http::template_renderer::TemplateRenderer; 13use smokesignal::http::macros::create_renderer; 14 15// Create renderer with all context 16let renderer = TemplateRenderer::new( 17 template_engine, 18 i18n_context, 19 language, 20 is_htmx 21).with_gender_context(gender_context); 22 23// Render template 24let html = renderer.render("template_name", &context)?; 25``` 26 27#### Constructor Methods 28 29##### `new()` 30```rust 31pub fn new( 32 template_engine: &'a Environment<'a>, 33 i18n_context: &'a I18nTemplateContext, 34 language: Language, 35 is_htmx: bool, 36) -> Self 37``` 38Creates a new TemplateRenderer with basic context. 39 40**Parameters:** 41- `template_engine`: minijinja Environment for template rendering 42- `i18n_context`: I18n context for translation support 43- `language`: Current user language (Language wrapper) 44- `is_htmx`: Whether this is an HTMX request 45 46##### `with_gender_context()` 47```rust 48pub fn with_gender_context(self, gender_context: Option<&'a GenderContext>) -> Self 49``` 50Adds gender context for personalized content. 51 52**Parameters:** 53- `gender_context`: Optional gender context for gendered translations 54 55#### Rendering Methods 56 57##### `render()` 58```rust 59pub fn render(&self, template_name: &str, context: &impl Serialize) -> Result<String, TemplateError> 60``` 61Renders a template with automatic context enrichment. 62 63**Parameters:** 64- `template_name`: Name of template (without extension) 65- `context`: Template context data 66 67**Returns:** Rendered HTML string or error 68 69##### `render_error()` 70```rust 71pub fn render_error(&self, template_name: &str, context: &impl Serialize, error_context: Value) -> Result<String, TemplateError> 72``` 73Renders error templates with merged context. 74 75**Parameters:** 76- `template_name`: Error template name 77- `context`: Base template context 78- `error_context`: Additional error-specific context 79 80#### Context Enrichment 81 82The TemplateRenderer automatically adds the following to all template contexts: 83 84```rust 85// Base context automatically added 86{ 87 "locale": "en-us", // Current language 88 "is_htmx": false, // HTMX request detection 89 "gender": "neutral", // User gender preference 90 "tr": TranslationFunction, // Translation function 91 "current_locale": LocaleFunction, // Current locale function 92 "has_locale": HasLocaleFunction, // Locale availability check 93} 94``` 95 96### Template Selection 97 98Templates are automatically selected based on: 99 1001. **Language**: `template.{locale}.html` 1012. **Request Type**: 102 - `.partial.html` for HTMX requests 103 - `.bare.html` for bare requests 104 - `.html` for full page requests 1053. **Fallback**: Falls back to English if locale template missing 106 107#### Template Naming Convention 108 109``` 110templates/ 111├── home.en-us.html # Full English template 112├── home.en-us.partial.html # HTMX partial 113├── home.en-us.bare.html # Bare template (no layout) 114├── home.fr-ca.html # Full French template 115├── home.fr-ca.partial.html # HTMX partial (French) 116└── error.en-us.html # Error template 117``` 118 119### Macros 120 121#### `create_renderer!` 122```rust 123create_renderer!(template_engine, i18n_context, language, is_htmx, gender_context) 124``` 125Convenience macro for creating TemplateRenderer instances. 126 127#### `contextual_error!` 128```rust 129contextual_error!(renderer, "error_template", error_context) 130``` 131Renders error templates with context preservation. 132 133### I18n Integration 134 135#### I18nTemplateContext 136 137Provides dynamic locale support for templates. 138 139```rust 140use smokesignal::i18n::I18nTemplateContext; 141 142// Create context 143let i18n_context = I18nTemplateContext::new(loader); 144 145// Use in templates via TemplateRenderer 146let renderer = TemplateRenderer::new(engine, &i18n_context, language, is_htmx); 147``` 148 149#### Translation Functions 150 151Templates have access to several i18n functions: 152 153##### `tr(key, args)` 154```html 155<!-- Basic translation --> 156{{ tr("welcome-message") }} 157 158<!-- Translation with arguments --> 159{{ tr("user-greeting", {"name": user.name}) }} 160 161<!-- Gender-aware translation (French) --> 162{{ tr("welcome-user", {"name": user.name, "gender": user.gender}) }} 163``` 164 165##### `current_locale()` 166```html 167<!-- Get current locale --> 168<html lang="{{ current_locale() }}"> 169``` 170 171##### `has_locale(locale)` 172```html 173<!-- Check if locale is available --> 174{% if has_locale("fr-ca") %} 175 <a href="/fr-ca/">Français</a> 176{% endif %} 177``` 178 179## Language and Locale Management 180 181### Language Wrapper 182 183The `Language` type wraps `LanguageIdentifier` for consistent handling: 184 185```rust 186use smokesignal::i18n::Language; 187 188// Create from identifier 189let language = Language(langid!("en-US")); 190 191// Access identifier 192let id = language.0; 193``` 194 195### Supported Languages 196 197```rust 198use smokesignal::i18n::SUPPORTED_LANGUAGES; 199 200// Available languages 201const SUPPORTED_LANGUAGES: &[&str] = &["en-us", "fr-ca"]; 202``` 203 204### Language Detection 205 206```rust 207use smokesignal::http::middleware_i18n::{ 208 detect_language_from_headers, 209 detect_language_from_cookie, 210 language_matches_any 211}; 212 213// Detect from Accept-Language header 214let language = detect_language_from_headers(&headers)?; 215 216// Detect from cookie 217let language = detect_language_from_cookie(&cookie_value)?; 218 219// Check if language is supported 220let is_supported = language_matches_any(&language); 221``` 222 223## Error Handling 224 225### TemplateError 226 227```rust 228use smokesignal::http::template_renderer::TemplateError; 229 230pub enum TemplateError { 231 RenderError(minijinja::Error), 232 ContextError(String), 233} 234``` 235 236### Error Context 237 238Error templates receive merged context: 239 240```rust 241// Base context + error context 242{ 243 "error_message": "Translation key failed", 244 "error_code": "TEMPLATE_001", 245 "debug_info": {...}, 246 // ... plus all base template context 247} 248``` 249 250## HTMX Integration 251 252### HTMX Detection 253 254```rust 255use smokesignal::http::middleware_i18n::{HX_REQUEST, HX_TRIGGER}; 256 257// Check headers 258let is_htmx = headers.get(HX_REQUEST).is_some(); 259let trigger = headers.get(HX_TRIGGER); 260``` 261 262### Partial Rendering 263 264HTMX requests automatically use `.partial.html` templates: 265 266```rust 267// Automatically selects: 268// - home.en-us.partial.html for HTMX 269// - home.en-us.html for full requests 270let html = renderer.render("home", &context)?; 271``` 272 273## Gender Context 274 275### GenderContext 276 277```rust 278use smokesignal::i18n::gender::{Gender, GenderContext}; 279 280// Create gender context 281let gender_context = GenderContext { 282 user_gender: Some(Gender::Female), 283 target_gender: None, 284}; 285 286// Use in renderer 287let renderer = renderer.with_gender_context(Some(&gender_context)); 288``` 289 290### Gender Values 291 292```rust 293pub enum Gender { 294 Male, // "male" 295 Female, // "female" 296 Neutral, // "neutral" 297} 298``` 299 300Gender values are automatically available in templates as strings. 301 302## Performance Considerations 303 304### Compile-Time Optimizations 305 306- **Static Translation Loading**: All translations compiled into binary 307- **Template Validation**: Templates validated at compile time 308- **Zero Runtime I/O**: No file system access for translations 309 310### Runtime Optimizations 311 312- **HTMX Header Caching**: Constants for faster header parsing 313- **Early Exit Language Detection**: Optimized language matching 314- **Context Reuse**: Efficient context composition 315 316### Memory Usage 317 318- **Shared References**: Template engines and i18n contexts are shared 319- **Strategic Cloning**: Only clone when necessary for API compatibility 320- **Minimal Allocations**: Efficient string handling in translations 321 322## Migration Guide 323 324### From Old System 325 326The new system is backwards compatible, but for optimal performance: 327 3281. **Replace direct template calls**: 329 ```rust 330 // Old 331 template_engine.render("template", &context)?; 332 333 // New 334 let renderer = create_renderer!(engine, i18n, lang, htmx, gender); 335 renderer.render("template", &context)?; 336 ``` 337 3382. **Use unified error handling**: 339 ```rust 340 // Old 341 if let Err(e) = result { 342 return render_error_template(&e); 343 } 344 345 // New 346 contextual_error!(renderer, "error_template", error_context) 347 ``` 348 3493. **Leverage automatic context**: 350 ```rust 351 // Old 352 let mut context = minijinja::context! { ... }; 353 context.insert("locale", &locale); 354 context.insert("is_htmx", is_htmx); 355 356 // New - automatic 357 renderer.render("template", &base_context)?; 358 ``` 359 360### Best Practices 361 3621. **Use create_renderer! macro** for concise renderer creation 3632. **Leverage automatic context** instead of manual context building 3643. **Use contextual_error!** for consistent error handling 3654. **Test with multiple locales** to ensure fallback behavior 3665. **Validate gender variants** for French Canadian content 367 368## Testing 369 370### Unit Tests 371 372```rust 373#[cfg(test)] 374mod tests { 375 use super::*; 376 377 #[test] 378 fn test_template_rendering() { 379 let renderer = create_test_renderer(); 380 let html = renderer.render("test", &context!{}).unwrap(); 381 assert!(html.contains("expected content")); 382 } 383} 384``` 385 386### Integration Tests 387 388```bash 389# Test entire i18n system 390cargo test i18n 391 392# Test template rendering 393cargo test template_renderer 394 395# Test specific language 396cargo test i18n -- fr_ca 397```