forked from
smokesignal.events/smokesignal
i18n+filtering fork - fluent-templates v2
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```