A Django application that facilitates the translation process of your Django projects
—
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.
Filters for processing and displaying translation-related data in templates.
def can_translate(user) -> bool:
"""
Check user translation permissions in templates.
Template filter that checks if a user has permission to access
Rosetta translation functionality. Uses the same permission logic
as the programmatic can_translate() function.
Usage in templates:
{% if user|can_translate %}
<a href="{% url 'rosetta-file-list' %}">Translations</a>
{% endif %}
Parameters:
- user: Django User instance
Returns:
Boolean indicating translation access permission
"""
def format_message(message: str) -> str:
"""
Format message text with HTML escaping and line break handling.
Processes translation message text for safe display in HTML templates,
handling special characters, line breaks, and preserving formatting.
Usage in templates:
{{ message.msgstr|format_message|safe }}
Parameters:
- message: Raw message string from .po file
Returns:
HTML-safe formatted string with proper escaping and line breaks
"""
def lines_count(message: str) -> int:
"""
Count number of lines in message text.
Utility filter for determining text area sizing and layout
decisions based on message content length.
Usage in templates:
{% if message.msgstr|lines_count > 3 %}
<textarea rows="5">{{ message.msgstr }}</textarea>
{% else %}
<input type="text" value="{{ message.msgstr }}">
{% endif %}
Parameters:
- message: Message string to count lines in
Returns:
Integer number of lines in the message
"""
def mult(value: int, multiplier: int) -> int:
"""
Multiply two numbers in templates.
Mathematical filter for template calculations where multiplication
is needed (pagination, sizing, calculations, etc.).
Usage in templates:
{{ page_size|mult:page_number }}
Parameters:
- value: First number (base value)
- multiplier: Second number (multiplier)
Returns:
Result of value * multiplier
"""
def minus(value: int, subtrahend: int) -> int:
"""
Subtract two numbers in templates.
Mathematical filter for template calculations where subtraction
is needed (counting, offsets, calculations, etc.).
Usage in templates:
{{ total_entries|minus:translated_entries }}
Parameters:
- value: First number (minuend)
- subtrahend: Second number (subtrahend)
Returns:
Result of value - subtrahend
"""
def gt(value: int, comparison: int) -> bool:
"""
Greater than comparison in templates.
Comparison filter for template conditional logic where
greater than comparison is needed.
Usage in templates:
{% if entries_count|gt:10 %}
<div class="pagination">...</div>
{% endif %}
Parameters:
- value: First number to compare
- comparison: Second number to compare against
Returns:
Boolean result of value > comparison
"""
def is_fuzzy(entry) -> bool:
"""
Check if translation entry is marked as fuzzy.
Filter for checking the fuzzy flag status of translation entries,
used for styling and conditional display in templates.
Usage in templates:
{% if entry|is_fuzzy %}
<span class="fuzzy-indicator">Fuzzy</span>
{% endif %}
Parameters:
- entry: Translation entry object from .po file
Returns:
Boolean indicating whether entry is marked as fuzzy
"""Custom template tags for enhanced functionality and UI components.
def increment(context, counter_name: str) -> str:
"""
Counter increment template tag.
Provides a counter that increments each time it's called within
a template context. Useful for numbering items, generating IDs,
or tracking iterations.
Usage in templates:
{% load rosetta %}
{% for item in items %}
<div id="item-{% increment 'item_counter' %}">{{ item }}</div>
{% endfor %}
Parameters:
- context: Template context dictionary
- counter_name: Name/key for the counter variable
Returns:
String representation of current counter value
Side effects:
- Increments counter in template context
- Creates counter if it doesn't exist (starts at 1)
"""
class IncrNode(template.Node):
"""
Template node implementation for increment tag.
Internal implementation class for the increment template tag,
handling counter state management and value rendering.
"""
def __init__(self, counter_name: str):
"""Initialize with counter name."""
self.counter_name = counter_name
def render(self, context) -> str:
"""
Render the current counter value and increment.
Parameters:
- context: Template context
Returns:
String representation of counter value
"""Template library instance and variable pattern matching.
register
"""
Django template library instance for Rosetta template tags and filters.
This is the standard Django template library registration object that
makes all Rosetta template tags and filters available when you use:
{% load rosetta %}
Provides access to:
- All template filters (can_translate, format_message, etc.)
- All template tags (increment)
- Custom template functionality
"""
rx
"""
Compiled regular expression for matching Django template variables.
Used internally for processing Django template variable patterns
in translation strings, such as:
- %(variable_name)s patterns
- {variable_name} patterns
- Template-specific variable syntax
Pattern matching helps preserve variable names during translation
processing and formatting operations.
"""<!-- Load Rosetta template tags -->
{% load rosetta %}
<!-- Check user permissions -->
{% if user|can_translate %}
<div class="admin-tools">
<a href="{% url 'rosetta-file-list' %}" class="btn btn-primary">
Manage Translations
</a>
</div>
{% endif %}
<!-- Display translation statistics -->
<div class="translation-stats">
<p>Translated: {{ stats.translated }}</p>
<p>Untranslated: {{ stats.untranslated }}</p>
<p>Remaining: {{ stats.total|minus:stats.translated }}</p>
</div>{% load rosetta %}
<!-- Format translation messages -->
<div class="translation-entry">
<div class="original-text">
{{ entry.msgid|format_message|safe }}
</div>
<div class="translated-text {% if entry|is_fuzzy %}fuzzy{% endif %}">
{{ entry.msgstr|format_message|safe }}
</div>
<!-- Conditional display based on message length -->
{% if entry.msgstr|lines_count > 3 %}
<textarea class="translation-input" rows="{% if entry.msgstr|lines_count|gt:5 %}10{% else %}5{% endif %}">
{{ entry.msgstr }}
</textarea>
{% else %}
<input type="text" class="translation-input" value="{{ entry.msgstr }}">
{% endif %}
</div>{% load rosetta %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">Previous</a>
{% endif %}
<!-- Show page numbers with calculations -->
{% for page_num in page_obj.paginator.page_range %}
{% if page_num|minus:page_obj.number|abs <= 2 %}
{% if page_num == page_obj.number %}
<span class="current-page">{{ page_num }}</span>
{% else %}
<a href="?page={{ page_num }}" class="page-link">{{ page_num }}</a>
{% endif %}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="page-link">Next</a>
{% endif %}
<!-- Show entry range -->
<div class="entry-info">
Showing {{ page_obj.start_index }} - {{ page_obj.end_index }}
of {{ page_obj.paginator.count }} entries
</div>
</div>{% load rosetta %}
<!-- Number items in a list -->
<div class="translation-entries">
{% for entry in entries %}
<div class="entry" id="entry-{% increment 'entry_counter' %}">
<div class="entry-number">
#{% increment 'display_counter' %}
</div>
<div class="entry-content">
<label for="input-{% increment 'input_counter' %}">
{{ entry.msgid|format_message|safe }}
</label>
<input type="text"
id="input-{{ input_counter }}"
name="msgstr_{{ entry.id }}"
value="{{ entry.msgstr }}">
</div>
</div>
{% endfor %}
</div>
<!-- Generate unique form elements -->
<form class="batch-operations">
{% for lang in languages %}
<div class="language-section">
<h3>{{ lang.name }}</h3>
{% for file in lang.files %}
<label>
<input type="checkbox"
name="selected_files"
value="{{ file.id }}"
id="file-{% increment 'file_counter' %}">
{{ file.name }}
</label>
{% endfor %}
</div>
{% endfor %}
</form><!-- custom_translation_interface.html -->
{% extends "admin/base_site.html" %}
{% load rosetta %}
{% block title %}Translation Management{% endblock %}
{% block content %}
{% if user|can_translate %}
<div class="translation-interface">
<header class="interface-header">
<h1>Translation Management</h1>
<div class="stats">
<span class="stat">
Total: {{ total_entries }}
</span>
<span class="stat">
Translated: {{ translated_entries }}
</span>
<span class="stat">
Progress: {{ translated_entries|mult:100|div:total_entries }}%
</span>
</div>
</header>
<div class="entries-container">
{% for entry in entries %}
<div class="entry-row {% if entry|is_fuzzy %}fuzzy{% endif %}">
<div class="entry-number">
{% increment 'row_counter' %}
</div>
<div class="entry-original">
<strong>Original:</strong>
{{ entry.msgid|format_message|safe }}
</div>
<div class="entry-translation">
<label for="trans-{% increment 'trans_counter' %}">
Translation:
</label>
{% if entry.msgstr|lines_count|gt:2 %}
<textarea id="trans-{{ trans_counter }}"
name="msgstr_{{ entry.id }}"
rows="{{ entry.msgstr|lines_count|add:1 }}">{{ entry.msgstr }}</textarea>
{% else %}
<input type="text"
id="trans-{{ trans_counter }}"
name="msgstr_{{ entry.id }}"
value="{{ entry.msgstr }}">
{% endif %}
</div>
{% if entry|is_fuzzy %}
<div class="fuzzy-indicator">
<input type="checkbox"
name="fuzzy_{{ entry.id }}"
checked>
<label>Fuzzy</label>
</div>
{% endif %}
</div>
{% endfor %}
</div>
<div class="interface-footer">
<button type="submit" class="save-button">Save Translations</button>
<div class="pagination-info">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
({{ page_obj.paginator.count }} total entries)
</div>
</div>
</div>
{% else %}
<div class="access-denied">
<h2>Access Denied</h2>
<p>You don't have permission to access the translation interface.</p>
</div>
{% endif %}
{% endblock %}{% load rosetta %}
<script>
// Use template filters to provide data to JavaScript
const translationData = {
userCanTranslate: {% if user|can_translate %}true{% else %}false{% endif %},
totalEntries: {{ total_entries|default:0 }},
translatedEntries: {{ translated_entries|default:0 }},
completionPercentage: {{ translated_entries|mult:100|div:total_entries|default:0 }},
entriesPerPage: {{ entries_per_page|default:10 }}
};
// Update progress indicators
function updateProgressBar() {
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = translationData.completionPercentage + '%';
progressBar.textContent = Math.round(translationData.completionPercentage) + '%';
}
}
// Auto-resize textareas based on content
document.addEventListener('DOMContentLoaded', function() {
const textareas = document.querySelectorAll('.translation-input[data-lines]');
textareas.forEach(textarea => {
const lines = parseInt(textarea.dataset.lines);
if (lines > 3) {
textarea.rows = Math.min(lines + 1, 10);
}
});
});
</script>
<!-- Template with JavaScript integration -->
{% for entry in entries %}
<textarea class="translation-input"
data-lines="{{ entry.msgstr|lines_count }}"
data-entry-id="{{ entry.id }}"
data-is-fuzzy="{% if entry|is_fuzzy %}true{% else %}false{% endif %}">
{{ entry.msgstr }}
</textarea>
{% endfor %}Install with Tessl CLI
npx tessl i tessl/pypi-django-rosetta