Fork i18n + search + filtering- v0.2
at main 376 lines 11 kB view raw view rendered
1# Smokesignal Template Migration Guidelines 2 3This document provides step-by-step guidance for migrating existing hardcoded templates to use the new i18n system with on-demand translation functions. 4 5## Migration Overview 6 7Migrate from hardcoded strings in templates to Fluent-based translations using template functions. This migration eliminates pre-rendered translation HashMaps and improves HTMX performance. 8 9## Migration Strategy 10 11### Phase 1: Template Analysis & Key Extraction 12 13#### 1.1 Inventory Existing Strings 14```bash 15# Find all hardcoded strings in templates 16find templates/ -name "*.html" -exec grep -Hn '"[^"]*"' {} \; > strings_inventory.txt 17find templates/ -name "*.html" -exec grep -Hn "'[^']*'" {} \; >> strings_inventory.txt 18 19# Categorize by domain for organized migration 20grep -E "(button|btn|submit|save|edit|delete|cancel)" strings_inventory.txt > actions.txt 21grep -E "(error|fail|invalid|required)" strings_inventory.txt > errors.txt 22grep -E "(title|heading|h1|h2|h3)" strings_inventory.txt > headings.txt 23grep -E "(label|placeholder|hint)" strings_inventory.txt > forms.txt 24``` 25 26#### 1.2 Create Translation Key Naming Convention 27``` 28# Pattern: domain-purpose[-variant] 29save-changes # Basic action 30edit-profile # Specific action 31validation-required # Error message 32profile-title # Page heading 33enter-name-placeholder # Form guidance 34welcome-message-feminine # Gender variant 35``` 36 37### Phase 2: Fluent File Creation 38 39#### 2.1 Organize by Category 40```ftl 41# i18n/en-us/actions.ftl 42save-changes = Save Changes 43edit-profile = Edit Profile 44delete-item = Delete 45cancel-action = Cancel 46follow-user = Follow 47unfollow-user = Unfollow 48 49# i18n/en-us/errors.ftl 50validation-required = This field is required 51validation-email = Please enter a valid email 52validation-minlength = Must be at least {$min} characters 53form-submit-error = Unable to submit form 54profile-not-found = Profile not found 55 56# i18n/en-us/ui.ftl 57profile-title = Profile 58member-since = Member since 59events-created = Events Created 60welcome-message = Welcome 61search-placeholder = Search... 62 63# i18n/fr-ca/actions.ftl 64save-changes = Enregistrer les modifications 65edit-profile = Modifier le profil 66delete-item = Supprimer 67cancel-action = Annuler 68follow-user = Suivre 69unfollow-user = Ne plus suivre 70``` 71 72#### 2.2 Gender-Aware Translations 73```ftl 74# English (gender-neutral by default) 75welcome-message = Welcome 76profile-greeting = Hello there 77 78# French Canadian (gender variants) 79welcome-message = Bienvenue 80welcome-message-feminine = Bienvenue 81welcome-message-masculine = Bienvenu 82welcome-message-neutral = Bienvenue 83 84profile-greeting = Bonjour 85profile-greeting-feminine = Bonjour madame 86profile-greeting-masculine = Bonjour monsieur 87profile-greeting-neutral = Bonjour 88``` 89 90### Phase 3: Template Function Integration 91 92#### 3.1 Replace Simple Strings 93```html 94<!-- Before --> 95<button class="button">Save Changes</button> 96<h1>Profile</h1> 97<p>Member since {{ profile.created_at }}</p> 98 99<!-- After --> 100<button class="button">{{ t(key="save-changes", locale=locale) }}</button> 101<h1>{{ t(key="profile-title", locale=locale) }}</h1> 102<p>{{ t(key="member-since", locale=locale) }} {{ profile.created_at }}</p> 103``` 104 105#### 3.2 Add Gender-Aware Translations 106```html 107<!-- Before --> 108<h2>Welcome, {{ user.name }}!</h2> 109 110<!-- After --> 111<h2>{{ tg(key="welcome-message", locale=locale, gender=user_gender) }}, {{ user.name }}!</h2> 112``` 113 114#### 3.3 Handle Parameterized Messages 115```html 116<!-- Before --> 117<p>You have {{ event_count }} events</p> 118 119<!-- After --> 120<p>{{ tc(key="events-count", locale=locale, count=event_count) }}</p> 121``` 122 123### Phase 4: HTMX-Specific Migration 124 125#### 4.1 Form Templates with Language Propagation 126```html 127<!-- Before --> 128<form hx-post="/profile/update" hx-target="#profile-content"> 129 <label>Display Name</label> 130 <input name="display_name" placeholder="Enter your name" /> 131 <button type="submit">Save Changes</button> 132</form> 133 134<!-- After --> 135<form hx-post="/profile/update" 136 hx-target="#profile-content" 137 hx-headers='{"HX-Current-Language": "{{ locale }}"}'> 138 139 <label>{{ t(key="display-name", locale=locale) }}</label> 140 <input name="display_name" 141 placeholder="{{ t(key="enter-name-placeholder", locale=locale) }}" /> 142 <button type="submit">{{ t(key="save-changes", locale=locale) }}</button> 143</form> 144``` 145 146#### 4.2 Error Message Templates 147```html 148<!-- Before --> 149<div class="error">Invalid email address</div> 150 151<!-- After --> 152<div class="error">{{ t(key="validation-email", locale=locale) }}</div> 153``` 154 155### Phase 5: Template Hierarchy Migration 156 157#### 5.1 Base Template Updates 158```html 159<!-- templates/base.en-us.html --> 160<!doctype html> 161<html lang="{{ language }}"> 162<head> 163 <title>{{ t(key="site-title", locale=locale) }}</title> 164 <meta name="description" content="{{ t(key="site-description", locale=locale) }}"> 165</head> 166<body data-current-language="{{ locale }}"> 167 {% include 'nav.html' %} 168 {% block content %}{% endblock %} 169 {% include 'footer.html' %} 170</body> 171</html> 172``` 173 174#### 5.2 Partial Templates for HTMX 175```html 176<!-- templates/partials/profile_form.html --> 177<div id="profile-form" data-current-language="{{ locale }}"> 178 <h3>{{ t(key="edit-profile-title", locale=locale) }}</h3> 179 180 {% if errors %} 181 <div class="errors"> 182 {% for error in errors %} 183 <p class="error">{{ t(key=error.key, locale=locale) }}</p> 184 {% endfor %} 185 </div> 186 {% endif %} 187 188 <form hx-post="/profile/update" 189 hx-target="#profile-content" 190 hx-headers='{"HX-Current-Language": "{{ locale }}"}'> 191 <!-- Form fields with translations --> 192 </form> 193</div> 194``` 195 196## Migration Tools & Automation 197 198### Automated String Replacement Script 199```bash 200#!/bin/bash 201# migrate_template.sh 202 203TEMPLATE_FILE=$1 204BACKUP_FILE="${TEMPLATE_FILE}.bak" 205 206# Create backup 207cp "$TEMPLATE_FILE" "$BACKUP_FILE" 208 209# Replace common patterns 210sed -i 's/"Save Changes"/{{ t(key="save-changes", locale=locale) }}/g' "$TEMPLATE_FILE" 211sed -i 's/"Edit Profile"/{{ t(key="edit-profile", locale=locale) }}/g' "$TEMPLATE_FILE" 212sed -i 's/"Delete"/{{ t(key="delete-item", locale=locale) }}/g' "$TEMPLATE_FILE" 213sed -i 's/"Cancel"/{{ t(key="cancel-action", locale=locale) }}/g' "$TEMPLATE_FILE" 214 215# Handle form labels 216sed -i 's/"Display Name"/{{ t(key="display-name", locale=locale) }}/g' "$TEMPLATE_FILE" 217sed -i 's/"Email"/{{ t(key="email", locale=locale) }}/g' "$TEMPLATE_FILE" 218 219echo "Migrated $TEMPLATE_FILE (backup: $BACKUP_FILE)" 220``` 221 222### Translation Key Validator 223```rust 224// tools/validate_keys.rs 225use std::collections::HashSet; 226use regex::Regex; 227 228fn extract_translation_keys_from_templates() -> HashSet<String> { 229 let re = Regex::new(r#"\{\{\s*t\w*\(key="([^"]+)""#).unwrap(); 230 // Extract all translation keys from templates 231 // Return set of used keys 232} 233 234fn load_fluent_keys() -> HashSet<String> { 235 // Load all keys from .ftl files 236 // Return set of available keys 237} 238 239#[test] 240fn test_all_translation_keys_exist() { 241 let used_keys = extract_translation_keys_from_templates(); 242 let available_keys = load_fluent_keys(); 243 244 for key in &used_keys { 245 assert!( 246 available_keys.contains(key), 247 "Missing translation key: {} (used in templates)", 248 key 249 ); 250 } 251 252 println!("✅ All {} translation keys validated", used_keys.len()); 253} 254``` 255 256## Migration Validation 257 258### Template Syntax Validation 259```bash 260# Validate template syntax after migration 261find templates/ -name "*.html" -exec python3 -c " 262import sys 263import re 264 265def validate_template(file_path): 266 with open(file_path, 'r') as f: 267 content = f.read() 268 269 # Check for proper function calls 270 pattern = r'\{\{\s*t[gc]?\(key=[\"'\''][^\"\']+[\"'\''][^}]*\)\s*\}\}' 271 matches = re.findall(pattern, content) 272 273 # Check for missing locale parameter 274 missing_locale = re.findall(r'\{\{\s*t[gc]?\([^}]*\)\s*\}\}', content) 275 276 print(f'File: {file_path}') 277 print(f' Translation calls: {len(matches)}') 278 if missing_locale: 279 print(f' ⚠️ Potential missing locale: {len(missing_locale)}') 280 281validate_template(sys.argv[1]) 282" {} \; 283``` 284 285### Performance Comparison 286```rust 287// Compare before/after performance 288#[cfg(test)] 289mod migration_performance_tests { 290 #[test] 291 fn benchmark_old_vs_new_rendering() { 292 // Test pre-rendered HashMap approach vs on-demand functions 293 let start = std::time::Instant::now(); 294 295 // Old approach: pre-render all translations 296 let _old_result = render_with_prerendered_translations(); 297 let old_duration = start.elapsed(); 298 299 let start = std::time::Instant::now(); 300 301 // New approach: on-demand translation functions 302 let _new_result = render_with_template_functions(); 303 let new_duration = start.elapsed(); 304 305 println!("Old approach: {:?}", old_duration); 306 println!("New approach: {:?}", new_duration); 307 308 // Expect significant improvement 309 assert!(new_duration < old_duration * 3 / 4); 310 } 311} 312``` 313 314## Migration Checklist 315 316### Per Template 317- [ ] Backup original template 318- [ ] Extract all hardcoded strings 319- [ ] Create corresponding Fluent keys 320- [ ] Replace strings with template functions 321- [ ] Add HTMX language headers if applicable 322- [ ] Test rendering in both languages 323- [ ] Validate gender variants (if applicable) 324- [ ] Performance test HTMX interactions 325 326### Per Handler 327- [ ] Remove pre-rendered translation HashMap 328- [ ] Use minimal template context 329- [ ] Ensure Language extractor is used 330- [ ] Add proper error handling for missing keys 331- [ ] Test with HTMX requests 332 333### Project-Wide 334- [ ] All templates migrated 335- [ ] All Fluent files complete 336- [ ] Translation key validator passes 337- [ ] HTMX language propagation working 338- [ ] Performance benchmarks improved 339- [ ] Documentation updated 340 341## Common Migration Patterns 342 343### Form Validation Messages 344```html 345<!-- Pattern for validation errors --> 346{% if field_errors %} 347 <div class="field-errors"> 348 {% for error in field_errors %} 349 <span class="error"> 350 {{ t(key=error.translation_key, locale=locale, args=error.args) }} 351 </span> 352 {% endfor %} 353 </div> 354{% endif %} 355``` 356 357### Conditional Gender Messages 358```html 359<!-- Pattern for conditional gender content --> 360{% if user_gender == "feminine" %} 361 {{ tg(key="welcome-message", locale=locale, gender="feminine") }} 362{% elif user_gender == "masculine" %} 363 {{ tg(key="welcome-message", locale=locale, gender="masculine") }} 364{% else %} 365 {{ tg(key="welcome-message", locale=locale, gender="neutral") }} 366{% endif %} 367``` 368 369### Count-Based Messages 370```html 371<!-- Pattern for pluralization --> 372<p>{{ tc(key="events-created", locale=locale, count=profile.event_count) }}</p> 373<p>{{ tc(key="followers-count", locale=locale, count=profile.followers) }}</p> 374``` 375 376This migration approach ensures a smooth transition from hardcoded strings to a flexible, performance-optimized i18n system while maintaining HTMX compatibility.