A Django application that facilitates the translation process of your Django projects
—
Utilities for discovering, processing, and managing .po/.mo translation files across Django projects. These functions handle the core file system operations needed for translation management, including file discovery, timestamp handling, and pagination utilities.
Core function for locating .po translation files across different types of Django applications.
def find_pos(lang: str, project_apps: bool = True, django_apps: bool = False, third_party_apps: bool = False) -> list:
"""
Find .po files for specified language across application types.
Searches for translation files in different categories of Django applications
based on the provided parameters. Returns list of file paths with metadata.
Parameters:
- lang: Language code to search for (e.g., 'en', 'fr', 'de')
- project_apps: Include project-specific applications (default: True)
- django_apps: Include Django framework applications (default: False)
- third_party_apps: Include third-party applications (default: False)
Returns:
List of dictionaries containing:
- 'po_path': Full path to .po file
- 'mo_path': Full path to corresponding .mo file
- 'app_name': Application name
- 'domain': Translation domain (e.g., 'django', 'djangojs')
- 'is_writable': Boolean indicating write permissions
- 'stats': Statistics dict with 'translated', 'untranslated', 'fuzzy' counts
Respects:
- ROSETTA_EXCLUDED_APPLICATIONS setting
- ROSETTA_EXCLUDED_PATHS setting
- ROSETTA_POFILENAMES setting for allowed filenames
"""Function for generating properly formatted timestamps with timezone information.
def timestamp_with_timezone(dt=None) -> str:
"""
Generate timestamp with timezone information.
Creates timestamp string suitable for .po file headers and logging,
following GNU gettext conventions for date formatting.
Parameters:
- dt: datetime object to format (optional, defaults to current time)
Returns:
Formatted timestamp string with timezone (e.g., "2025-01-15 10:30 +0000")
Format follows .po file header requirements:
- YYYY-MM-DD HH:MM+ZZZZ format
- Uses system timezone if available
- Falls back to UTC if timezone cannot be determined
"""Function for generating pagination ranges for the web interface.
def pagination_range(first: int, last: int, current: int) -> list:
"""
Generate pagination ranges for UI display.
Creates smart pagination ranges that show relevant page numbers
around the current page, with ellipsis for gaps.
Parameters:
- first: First page number (typically 1)
- last: Last page number
- current: Current page number
Returns:
List of page numbers and/or ellipsis strings for display
Examples:
- pagination_range(1, 10, 1) -> [1, 2, 3, '...', 10]
- pagination_range(1, 10, 5) -> [1, '...', 4, 5, 6, '...', 10]
- pagination_range(1, 5, 3) -> [1, 2, 3, 4, 5]
"""Cache instance used for file operation caching and optimization.
cache
"""
Django cache instance for Rosetta file operations.
Used for:
- Caching file discovery results
- Storing .po file statistics
- Optimizing repeated file system access
- Temporary storage of file metadata
Cache key patterns:
- 'rosetta_file_list_{lang}_{hash}': File discovery results
- 'rosetta_po_stats_{path_hash}': .po file statistics
- 'rosetta_app_list': Application discovery results
"""from rosetta.poutil import find_pos
def discover_translation_files():
"""Discover all available translation files."""
# Find French translation files in project applications only
french_files = find_pos('fr', project_apps=True, django_apps=False, third_party_apps=False)
for file_info in french_files:
print(f"App: {file_info['app_name']}")
print(f"Domain: {file_info['domain']}")
print(f"PO file: {file_info['po_path']}")
print(f"Writable: {file_info['is_writable']}")
print(f"Stats: {file_info['stats']}")
print("---")
# Find all translation files including Django and third-party apps
all_files = find_pos('es', project_apps=True, django_apps=True, third_party_apps=True)
return all_filesfrom rosetta.poutil import find_pos
def analyze_translation_progress(language):
"""Analyze translation progress for a language."""
files = find_pos(language)
total_stats = {
'translated': 0,
'untranslated': 0,
'fuzzy': 0
}
app_stats = {}
for file_info in files:
app_name = file_info['app_name']
stats = file_info['stats']
# Aggregate totals
for key in total_stats:
total_stats[key] += stats.get(key, 0)
# Per-app statistics
if app_name not in app_stats:
app_stats[app_name] = {'translated': 0, 'untranslated': 0, 'fuzzy': 0}
for key in app_stats[app_name]:
app_stats[app_name][key] += stats.get(key, 0)
# Calculate percentages
total_entries = sum(total_stats.values())
if total_entries > 0:
completion_percentage = (total_stats['translated'] / total_entries) * 100
else:
completion_percentage = 0
return {
'language': language,
'total_stats': total_stats,
'app_stats': app_stats,
'completion_percentage': completion_percentage
}from rosetta.poutil import find_pos
def find_writable_files(language):
"""Find only writable translation files for editing."""
all_files = find_pos(language, project_apps=True, django_apps=True)
# Filter for writable files only
writable_files = [
file_info for file_info in all_files
if file_info['is_writable']
]
return writable_files
def find_incomplete_files(language, threshold=50):
"""Find files with translation completion below threshold."""
all_files = find_pos(language)
incomplete_files = []
for file_info in all_files:
stats = file_info['stats']
total = stats.get('translated', 0) + stats.get('untranslated', 0) + stats.get('fuzzy', 0)
if total > 0:
completion = (stats.get('translated', 0) / total) * 100
if completion < threshold:
file_info['completion_percentage'] = completion
incomplete_files.append(file_info)
return incomplete_filesfrom rosetta.poutil import timestamp_with_timezone
from datetime import datetime, timezone
def update_po_file_header():
"""Generate timestamps for .po file headers."""
# Current timestamp
current_timestamp = timestamp_with_timezone()
print(f"Current: {current_timestamp}")
# Specific datetime
specific_time = datetime(2025, 1, 15, 10, 30, 0, tzinfo=timezone.utc)
specific_timestamp = timestamp_with_timezone(specific_time)
print(f"Specific: {specific_timestamp}")
# Use in .po file header
po_header = f'''# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\\n"
"Report-Msgid-Bugs-To: \\n"
"POT-Creation-Date: {current_timestamp}\\n"
"PO-Revision-Date: {current_timestamp}\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: LANGUAGE <LL@li.org>\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
'''
return po_headerfrom rosetta.poutil import pagination_range
def generate_pagination_context(current_page, total_pages):
"""Generate pagination context for templates."""
if total_pages <= 1:
return {'show_pagination': False}
# Generate page range
page_range = pagination_range(1, total_pages, current_page)
# Build pagination context
context = {
'show_pagination': True,
'current_page': current_page,
'total_pages': total_pages,
'page_range': page_range,
'has_previous': current_page > 1,
'has_next': current_page < total_pages,
'previous_page': current_page - 1 if current_page > 1 else None,
'next_page': current_page + 1 if current_page < total_pages else None,
}
return context
# Usage in views
def translation_list_view(request):
from django.core.paginator import Paginator
from rosetta.conf import settings as rosetta_settings
# Get translation entries
entries = get_translation_entries() # Your function to get entries
# Paginate
paginator = Paginator(entries, rosetta_settings.MESSAGES_PER_PAGE)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
# Generate pagination context
pagination_context = generate_pagination_context(
page_obj.number,
paginator.num_pages
)
return render(request, 'template.html', {
'page_obj': page_obj,
'pagination': pagination_context
})import os
from rosetta.poutil import find_pos, cache
def monitor_translation_files(language):
"""Monitor translation files for changes."""
files = find_pos(language)
file_info = {}
for file_data in files:
po_path = file_data['po_path']
if os.path.exists(po_path):
stat = os.stat(po_path)
file_info[po_path] = {
'mtime': stat.st_mtime,
'size': stat.st_size,
'is_writable': os.access(po_path, os.W_OK)
}
# Cache file information for comparison
cache_key = f'rosetta_file_monitor_{language}'
previous_info = cache.get(cache_key, {})
cache.set(cache_key, file_info, 3600) # Cache for 1 hour
# Detect changes
changed_files = []
for path, info in file_info.items():
if path in previous_info:
prev_info = previous_info[path]
if (info['mtime'] != prev_info['mtime'] or
info['size'] != prev_info['size']):
changed_files.append(path)
else:
# New file
changed_files.append(path)
return changed_files
def clear_file_caches():
"""Clear all file-related caches."""
# Clear file discovery caches
cache_keys_to_clear = []
# You would need to implement cache key pattern matching
# This is a simplified example
for key in cache._cache.keys():
if key.startswith('rosetta_file_'):
cache_keys_to_clear.append(key)
for key in cache_keys_to_clear:
cache.delete(key)Install with Tessl CLI
npx tessl i tessl/pypi-django-rosetta