0
# Template Integration
1
2
Django template tags and filters for integrating Rosetta functionality into custom templates and extending the web interface. These template utilities provide access to translation permissions, formatting functions, and UI enhancements directly within Django templates.
3
4
## Capabilities
5
6
### Template Filters
7
8
Filters for processing and displaying translation-related data in templates.
9
10
```python { .api }
11
def can_translate(user) -> bool:
12
"""
13
Check user translation permissions in templates.
14
15
Template filter that checks if a user has permission to access
16
Rosetta translation functionality. Uses the same permission logic
17
as the programmatic can_translate() function.
18
19
Usage in templates:
20
{% if user|can_translate %}
21
<a href="{% url 'rosetta-file-list' %}">Translations</a>
22
{% endif %}
23
24
Parameters:
25
- user: Django User instance
26
27
Returns:
28
Boolean indicating translation access permission
29
"""
30
31
def format_message(message: str) -> str:
32
"""
33
Format message text with HTML escaping and line break handling.
34
35
Processes translation message text for safe display in HTML templates,
36
handling special characters, line breaks, and preserving formatting.
37
38
Usage in templates:
39
{{ message.msgstr|format_message|safe }}
40
41
Parameters:
42
- message: Raw message string from .po file
43
44
Returns:
45
HTML-safe formatted string with proper escaping and line breaks
46
"""
47
48
def lines_count(message: str) -> int:
49
"""
50
Count number of lines in message text.
51
52
Utility filter for determining text area sizing and layout
53
decisions based on message content length.
54
55
Usage in templates:
56
{% if message.msgstr|lines_count > 3 %}
57
<textarea rows="5">{{ message.msgstr }}</textarea>
58
{% else %}
59
<input type="text" value="{{ message.msgstr }}">
60
{% endif %}
61
62
Parameters:
63
- message: Message string to count lines in
64
65
Returns:
66
Integer number of lines in the message
67
"""
68
69
def mult(value: int, multiplier: int) -> int:
70
"""
71
Multiply two numbers in templates.
72
73
Mathematical filter for template calculations where multiplication
74
is needed (pagination, sizing, calculations, etc.).
75
76
Usage in templates:
77
{{ page_size|mult:page_number }}
78
79
Parameters:
80
- value: First number (base value)
81
- multiplier: Second number (multiplier)
82
83
Returns:
84
Result of value * multiplier
85
"""
86
87
def minus(value: int, subtrahend: int) -> int:
88
"""
89
Subtract two numbers in templates.
90
91
Mathematical filter for template calculations where subtraction
92
is needed (counting, offsets, calculations, etc.).
93
94
Usage in templates:
95
{{ total_entries|minus:translated_entries }}
96
97
Parameters:
98
- value: First number (minuend)
99
- subtrahend: Second number (subtrahend)
100
101
Returns:
102
Result of value - subtrahend
103
"""
104
105
def gt(value: int, comparison: int) -> bool:
106
"""
107
Greater than comparison in templates.
108
109
Comparison filter for template conditional logic where
110
greater than comparison is needed.
111
112
Usage in templates:
113
{% if entries_count|gt:10 %}
114
<div class="pagination">...</div>
115
{% endif %}
116
117
Parameters:
118
- value: First number to compare
119
- comparison: Second number to compare against
120
121
Returns:
122
Boolean result of value > comparison
123
"""
124
125
def is_fuzzy(entry) -> bool:
126
"""
127
Check if translation entry is marked as fuzzy.
128
129
Filter for checking the fuzzy flag status of translation entries,
130
used for styling and conditional display in templates.
131
132
Usage in templates:
133
{% if entry|is_fuzzy %}
134
<span class="fuzzy-indicator">Fuzzy</span>
135
{% endif %}
136
137
Parameters:
138
- entry: Translation entry object from .po file
139
140
Returns:
141
Boolean indicating whether entry is marked as fuzzy
142
"""
143
```
144
145
### Template Tags
146
147
Custom template tags for enhanced functionality and UI components.
148
149
```python { .api }
150
def increment(context, counter_name: str) -> str:
151
"""
152
Counter increment template tag.
153
154
Provides a counter that increments each time it's called within
155
a template context. Useful for numbering items, generating IDs,
156
or tracking iterations.
157
158
Usage in templates:
159
{% load rosetta %}
160
{% for item in items %}
161
<div id="item-{% increment 'item_counter' %}">{{ item }}</div>
162
{% endfor %}
163
164
Parameters:
165
- context: Template context dictionary
166
- counter_name: Name/key for the counter variable
167
168
Returns:
169
String representation of current counter value
170
171
Side effects:
172
- Increments counter in template context
173
- Creates counter if it doesn't exist (starts at 1)
174
"""
175
176
class IncrNode(template.Node):
177
"""
178
Template node implementation for increment tag.
179
180
Internal implementation class for the increment template tag,
181
handling counter state management and value rendering.
182
"""
183
184
def __init__(self, counter_name: str):
185
"""Initialize with counter name."""
186
self.counter_name = counter_name
187
188
def render(self, context) -> str:
189
"""
190
Render the current counter value and increment.
191
192
Parameters:
193
- context: Template context
194
195
Returns:
196
String representation of counter value
197
"""
198
```
199
200
### Template Library Registration
201
202
Template library instance and variable pattern matching.
203
204
```python { .api }
205
register
206
"""
207
Django template library instance for Rosetta template tags and filters.
208
209
This is the standard Django template library registration object that
210
makes all Rosetta template tags and filters available when you use:
211
{% load rosetta %}
212
213
Provides access to:
214
- All template filters (can_translate, format_message, etc.)
215
- All template tags (increment)
216
- Custom template functionality
217
"""
218
219
rx
220
"""
221
Compiled regular expression for matching Django template variables.
222
223
Used internally for processing Django template variable patterns
224
in translation strings, such as:
225
- %(variable_name)s patterns
226
- {variable_name} patterns
227
- Template-specific variable syntax
228
229
Pattern matching helps preserve variable names during translation
230
processing and formatting operations.
231
"""
232
```
233
234
## Usage Examples
235
236
### Basic Template Integration
237
238
```html
239
<!-- Load Rosetta template tags -->
240
{% load rosetta %}
241
242
<!-- Check user permissions -->
243
{% if user|can_translate %}
244
<div class="admin-tools">
245
<a href="{% url 'rosetta-file-list' %}" class="btn btn-primary">
246
Manage Translations
247
</a>
248
</div>
249
{% endif %}
250
251
<!-- Display translation statistics -->
252
<div class="translation-stats">
253
<p>Translated: {{ stats.translated }}</p>
254
<p>Untranslated: {{ stats.untranslated }}</p>
255
<p>Remaining: {{ stats.total|minus:stats.translated }}</p>
256
</div>
257
```
258
259
### Message Formatting in Templates
260
261
```html
262
{% load rosetta %}
263
264
<!-- Format translation messages -->
265
<div class="translation-entry">
266
<div class="original-text">
267
{{ entry.msgid|format_message|safe }}
268
</div>
269
270
<div class="translated-text {% if entry|is_fuzzy %}fuzzy{% endif %}">
271
{{ entry.msgstr|format_message|safe }}
272
</div>
273
274
<!-- Conditional display based on message length -->
275
{% if entry.msgstr|lines_count > 3 %}
276
<textarea class="translation-input" rows="{% if entry.msgstr|lines_count|gt:5 %}10{% else %}5{% endif %}">
277
{{ entry.msgstr }}
278
</textarea>
279
{% else %}
280
<input type="text" class="translation-input" value="{{ entry.msgstr }}">
281
{% endif %}
282
</div>
283
```
284
285
### Pagination with Template Filters
286
287
```html
288
{% load rosetta %}
289
290
<div class="pagination">
291
{% if page_obj.has_previous %}
292
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">Previous</a>
293
{% endif %}
294
295
<!-- Show page numbers with calculations -->
296
{% for page_num in page_obj.paginator.page_range %}
297
{% if page_num|minus:page_obj.number|abs <= 2 %}
298
{% if page_num == page_obj.number %}
299
<span class="current-page">{{ page_num }}</span>
300
{% else %}
301
<a href="?page={{ page_num }}" class="page-link">{{ page_num }}</a>
302
{% endif %}
303
{% endif %}
304
{% endfor %}
305
306
{% if page_obj.has_next %}
307
<a href="?page={{ page_obj.next_page_number }}" class="page-link">Next</a>
308
{% endif %}
309
310
<!-- Show entry range -->
311
<div class="entry-info">
312
Showing {{ page_obj.start_index }} - {{ page_obj.end_index }}
313
of {{ page_obj.paginator.count }} entries
314
</div>
315
</div>
316
```
317
318
### Counter Usage with Increment Tag
319
320
```html
321
{% load rosetta %}
322
323
<!-- Number items in a list -->
324
<div class="translation-entries">
325
{% for entry in entries %}
326
<div class="entry" id="entry-{% increment 'entry_counter' %}">
327
<div class="entry-number">
328
#{% increment 'display_counter' %}
329
</div>
330
<div class="entry-content">
331
<label for="input-{% increment 'input_counter' %}">
332
{{ entry.msgid|format_message|safe }}
333
</label>
334
<input type="text"
335
id="input-{{ input_counter }}"
336
name="msgstr_{{ entry.id }}"
337
value="{{ entry.msgstr }}">
338
</div>
339
</div>
340
{% endfor %}
341
</div>
342
343
<!-- Generate unique form elements -->
344
<form class="batch-operations">
345
{% for lang in languages %}
346
<div class="language-section">
347
<h3>{{ lang.name }}</h3>
348
{% for file in lang.files %}
349
<label>
350
<input type="checkbox"
351
name="selected_files"
352
value="{{ file.id }}"
353
id="file-{% increment 'file_counter' %}">
354
{{ file.name }}
355
</label>
356
{% endfor %}
357
</div>
358
{% endfor %}
359
</form>
360
```
361
362
### Custom Template for Translation Interface
363
364
```html
365
<!-- custom_translation_interface.html -->
366
{% extends "admin/base_site.html" %}
367
{% load rosetta %}
368
369
{% block title %}Translation Management{% endblock %}
370
371
{% block content %}
372
{% if user|can_translate %}
373
<div class="translation-interface">
374
<header class="interface-header">
375
<h1>Translation Management</h1>
376
<div class="stats">
377
<span class="stat">
378
Total: {{ total_entries }}
379
</span>
380
<span class="stat">
381
Translated: {{ translated_entries }}
382
</span>
383
<span class="stat">
384
Progress: {{ translated_entries|mult:100|div:total_entries }}%
385
</span>
386
</div>
387
</header>
388
389
<div class="entries-container">
390
{% for entry in entries %}
391
<div class="entry-row {% if entry|is_fuzzy %}fuzzy{% endif %}">
392
<div class="entry-number">
393
{% increment 'row_counter' %}
394
</div>
395
396
<div class="entry-original">
397
<strong>Original:</strong>
398
{{ entry.msgid|format_message|safe }}
399
</div>
400
401
<div class="entry-translation">
402
<label for="trans-{% increment 'trans_counter' %}">
403
Translation:
404
</label>
405
406
{% if entry.msgstr|lines_count|gt:2 %}
407
<textarea id="trans-{{ trans_counter }}"
408
name="msgstr_{{ entry.id }}"
409
rows="{{ entry.msgstr|lines_count|add:1 }}">{{ entry.msgstr }}</textarea>
410
{% else %}
411
<input type="text"
412
id="trans-{{ trans_counter }}"
413
name="msgstr_{{ entry.id }}"
414
value="{{ entry.msgstr }}">
415
{% endif %}
416
</div>
417
418
{% if entry|is_fuzzy %}
419
<div class="fuzzy-indicator">
420
<input type="checkbox"
421
name="fuzzy_{{ entry.id }}"
422
checked>
423
<label>Fuzzy</label>
424
</div>
425
{% endif %}
426
</div>
427
{% endfor %}
428
</div>
429
430
<div class="interface-footer">
431
<button type="submit" class="save-button">Save Translations</button>
432
<div class="pagination-info">
433
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
434
({{ page_obj.paginator.count }} total entries)
435
</div>
436
</div>
437
</div>
438
{% else %}
439
<div class="access-denied">
440
<h2>Access Denied</h2>
441
<p>You don't have permission to access the translation interface.</p>
442
</div>
443
{% endif %}
444
{% endblock %}
445
```
446
447
### Template Integration with JavaScript
448
449
```html
450
{% load rosetta %}
451
452
<script>
453
// Use template filters to provide data to JavaScript
454
const translationData = {
455
userCanTranslate: {% if user|can_translate %}true{% else %}false{% endif %},
456
totalEntries: {{ total_entries|default:0 }},
457
translatedEntries: {{ translated_entries|default:0 }},
458
completionPercentage: {{ translated_entries|mult:100|div:total_entries|default:0 }},
459
entriesPerPage: {{ entries_per_page|default:10 }}
460
};
461
462
// Update progress indicators
463
function updateProgressBar() {
464
const progressBar = document.getElementById('progress-bar');
465
if (progressBar) {
466
progressBar.style.width = translationData.completionPercentage + '%';
467
progressBar.textContent = Math.round(translationData.completionPercentage) + '%';
468
}
469
}
470
471
// Auto-resize textareas based on content
472
document.addEventListener('DOMContentLoaded', function() {
473
const textareas = document.querySelectorAll('.translation-input[data-lines]');
474
textareas.forEach(textarea => {
475
const lines = parseInt(textarea.dataset.lines);
476
if (lines > 3) {
477
textarea.rows = Math.min(lines + 1, 10);
478
}
479
});
480
});
481
</script>
482
483
<!-- Template with JavaScript integration -->
484
{% for entry in entries %}
485
<textarea class="translation-input"
486
data-lines="{{ entry.msgstr|lines_count }}"
487
data-entry-id="{{ entry.id }}"
488
data-is-fuzzy="{% if entry|is_fuzzy %}true{% else %}false{% endif %}">
489
{{ entry.msgstr }}
490
</textarea>
491
{% endfor %}
492
```