0
# File Operations and Utilities
1
2
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.
3
4
## Capabilities
5
6
### Translation File Discovery
7
8
Core function for locating .po translation files across different types of Django applications.
9
10
```python { .api }
11
def find_pos(lang: str, project_apps: bool = True, django_apps: bool = False, third_party_apps: bool = False) -> list:
12
"""
13
Find .po files for specified language across application types.
14
15
Searches for translation files in different categories of Django applications
16
based on the provided parameters. Returns list of file paths with metadata.
17
18
Parameters:
19
- lang: Language code to search for (e.g., 'en', 'fr', 'de')
20
- project_apps: Include project-specific applications (default: True)
21
- django_apps: Include Django framework applications (default: False)
22
- third_party_apps: Include third-party applications (default: False)
23
24
Returns:
25
List of dictionaries containing:
26
- 'po_path': Full path to .po file
27
- 'mo_path': Full path to corresponding .mo file
28
- 'app_name': Application name
29
- 'domain': Translation domain (e.g., 'django', 'djangojs')
30
- 'is_writable': Boolean indicating write permissions
31
- 'stats': Statistics dict with 'translated', 'untranslated', 'fuzzy' counts
32
33
Respects:
34
- ROSETTA_EXCLUDED_APPLICATIONS setting
35
- ROSETTA_EXCLUDED_PATHS setting
36
- ROSETTA_POFILENAMES setting for allowed filenames
37
"""
38
```
39
40
### Timestamp Utilities
41
42
Function for generating properly formatted timestamps with timezone information.
43
44
```python { .api }
45
def timestamp_with_timezone(dt=None) -> str:
46
"""
47
Generate timestamp with timezone information.
48
49
Creates timestamp string suitable for .po file headers and logging,
50
following GNU gettext conventions for date formatting.
51
52
Parameters:
53
- dt: datetime object to format (optional, defaults to current time)
54
55
Returns:
56
Formatted timestamp string with timezone (e.g., "2025-01-15 10:30 +0000")
57
58
Format follows .po file header requirements:
59
- YYYY-MM-DD HH:MM+ZZZZ format
60
- Uses system timezone if available
61
- Falls back to UTC if timezone cannot be determined
62
"""
63
```
64
65
### Pagination Utilities
66
67
Function for generating pagination ranges for the web interface.
68
69
```python { .api }
70
def pagination_range(first: int, last: int, current: int) -> list:
71
"""
72
Generate pagination ranges for UI display.
73
74
Creates smart pagination ranges that show relevant page numbers
75
around the current page, with ellipsis for gaps.
76
77
Parameters:
78
- first: First page number (typically 1)
79
- last: Last page number
80
- current: Current page number
81
82
Returns:
83
List of page numbers and/or ellipsis strings for display
84
85
Examples:
86
- pagination_range(1, 10, 1) -> [1, 2, 3, '...', 10]
87
- pagination_range(1, 10, 5) -> [1, '...', 4, 5, 6, '...', 10]
88
- pagination_range(1, 5, 3) -> [1, 2, 3, 4, 5]
89
"""
90
```
91
92
### Cache Instance
93
94
Cache instance used for file operation caching and optimization.
95
96
```python { .api }
97
cache
98
"""
99
Django cache instance for Rosetta file operations.
100
101
Used for:
102
- Caching file discovery results
103
- Storing .po file statistics
104
- Optimizing repeated file system access
105
- Temporary storage of file metadata
106
107
Cache key patterns:
108
- 'rosetta_file_list_{lang}_{hash}': File discovery results
109
- 'rosetta_po_stats_{path_hash}': .po file statistics
110
- 'rosetta_app_list': Application discovery results
111
"""
112
```
113
114
## Usage Examples
115
116
### Basic File Discovery
117
118
```python
119
from rosetta.poutil import find_pos
120
121
def discover_translation_files():
122
"""Discover all available translation files."""
123
124
# Find French translation files in project applications only
125
french_files = find_pos('fr', project_apps=True, django_apps=False, third_party_apps=False)
126
127
for file_info in french_files:
128
print(f"App: {file_info['app_name']}")
129
print(f"Domain: {file_info['domain']}")
130
print(f"PO file: {file_info['po_path']}")
131
print(f"Writable: {file_info['is_writable']}")
132
print(f"Stats: {file_info['stats']}")
133
print("---")
134
135
# Find all translation files including Django and third-party apps
136
all_files = find_pos('es', project_apps=True, django_apps=True, third_party_apps=True)
137
138
return all_files
139
```
140
141
### File Statistics Analysis
142
143
```python
144
from rosetta.poutil import find_pos
145
146
def analyze_translation_progress(language):
147
"""Analyze translation progress for a language."""
148
149
files = find_pos(language)
150
151
total_stats = {
152
'translated': 0,
153
'untranslated': 0,
154
'fuzzy': 0
155
}
156
157
app_stats = {}
158
159
for file_info in files:
160
app_name = file_info['app_name']
161
stats = file_info['stats']
162
163
# Aggregate totals
164
for key in total_stats:
165
total_stats[key] += stats.get(key, 0)
166
167
# Per-app statistics
168
if app_name not in app_stats:
169
app_stats[app_name] = {'translated': 0, 'untranslated': 0, 'fuzzy': 0}
170
171
for key in app_stats[app_name]:
172
app_stats[app_name][key] += stats.get(key, 0)
173
174
# Calculate percentages
175
total_entries = sum(total_stats.values())
176
if total_entries > 0:
177
completion_percentage = (total_stats['translated'] / total_entries) * 100
178
else:
179
completion_percentage = 0
180
181
return {
182
'language': language,
183
'total_stats': total_stats,
184
'app_stats': app_stats,
185
'completion_percentage': completion_percentage
186
}
187
```
188
189
### Custom File Filtering
190
191
```python
192
from rosetta.poutil import find_pos
193
194
def find_writable_files(language):
195
"""Find only writable translation files for editing."""
196
197
all_files = find_pos(language, project_apps=True, django_apps=True)
198
199
# Filter for writable files only
200
writable_files = [
201
file_info for file_info in all_files
202
if file_info['is_writable']
203
]
204
205
return writable_files
206
207
def find_incomplete_files(language, threshold=50):
208
"""Find files with translation completion below threshold."""
209
210
all_files = find_pos(language)
211
incomplete_files = []
212
213
for file_info in all_files:
214
stats = file_info['stats']
215
total = stats.get('translated', 0) + stats.get('untranslated', 0) + stats.get('fuzzy', 0)
216
217
if total > 0:
218
completion = (stats.get('translated', 0) / total) * 100
219
if completion < threshold:
220
file_info['completion_percentage'] = completion
221
incomplete_files.append(file_info)
222
223
return incomplete_files
224
```
225
226
### Timestamp Generation
227
228
```python
229
from rosetta.poutil import timestamp_with_timezone
230
from datetime import datetime, timezone
231
232
def update_po_file_header():
233
"""Generate timestamps for .po file headers."""
234
235
# Current timestamp
236
current_timestamp = timestamp_with_timezone()
237
print(f"Current: {current_timestamp}")
238
239
# Specific datetime
240
specific_time = datetime(2025, 1, 15, 10, 30, 0, tzinfo=timezone.utc)
241
specific_timestamp = timestamp_with_timezone(specific_time)
242
print(f"Specific: {specific_timestamp}")
243
244
# Use in .po file header
245
po_header = f'''# SOME DESCRIPTIVE TITLE.
246
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
247
# This file is distributed under the same license as the PACKAGE package.
248
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
249
#
250
msgid ""
251
msgstr ""
252
"Project-Id-Version: PACKAGE VERSION\\n"
253
"Report-Msgid-Bugs-To: \\n"
254
"POT-Creation-Date: {current_timestamp}\\n"
255
"PO-Revision-Date: {current_timestamp}\\n"
256
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
257
"Language-Team: LANGUAGE <LL@li.org>\\n"
258
"MIME-Version: 1.0\\n"
259
"Content-Type: text/plain; charset=UTF-8\\n"
260
"Content-Transfer-Encoding: 8bit\\n"
261
'''
262
263
return po_header
264
```
265
266
### Pagination Implementation
267
268
```python
269
from rosetta.poutil import pagination_range
270
271
def generate_pagination_context(current_page, total_pages):
272
"""Generate pagination context for templates."""
273
274
if total_pages <= 1:
275
return {'show_pagination': False}
276
277
# Generate page range
278
page_range = pagination_range(1, total_pages, current_page)
279
280
# Build pagination context
281
context = {
282
'show_pagination': True,
283
'current_page': current_page,
284
'total_pages': total_pages,
285
'page_range': page_range,
286
'has_previous': current_page > 1,
287
'has_next': current_page < total_pages,
288
'previous_page': current_page - 1 if current_page > 1 else None,
289
'next_page': current_page + 1 if current_page < total_pages else None,
290
}
291
292
return context
293
294
# Usage in views
295
def translation_list_view(request):
296
from django.core.paginator import Paginator
297
from rosetta.conf import settings as rosetta_settings
298
299
# Get translation entries
300
entries = get_translation_entries() # Your function to get entries
301
302
# Paginate
303
paginator = Paginator(entries, rosetta_settings.MESSAGES_PER_PAGE)
304
page_number = request.GET.get('page', 1)
305
page_obj = paginator.get_page(page_number)
306
307
# Generate pagination context
308
pagination_context = generate_pagination_context(
309
page_obj.number,
310
paginator.num_pages
311
)
312
313
return render(request, 'template.html', {
314
'page_obj': page_obj,
315
'pagination': pagination_context
316
})
317
```
318
319
### File System Monitoring
320
321
```python
322
import os
323
from rosetta.poutil import find_pos, cache
324
325
def monitor_translation_files(language):
326
"""Monitor translation files for changes."""
327
328
files = find_pos(language)
329
file_info = {}
330
331
for file_data in files:
332
po_path = file_data['po_path']
333
334
if os.path.exists(po_path):
335
stat = os.stat(po_path)
336
file_info[po_path] = {
337
'mtime': stat.st_mtime,
338
'size': stat.st_size,
339
'is_writable': os.access(po_path, os.W_OK)
340
}
341
342
# Cache file information for comparison
343
cache_key = f'rosetta_file_monitor_{language}'
344
previous_info = cache.get(cache_key, {})
345
cache.set(cache_key, file_info, 3600) # Cache for 1 hour
346
347
# Detect changes
348
changed_files = []
349
for path, info in file_info.items():
350
if path in previous_info:
351
prev_info = previous_info[path]
352
if (info['mtime'] != prev_info['mtime'] or
353
info['size'] != prev_info['size']):
354
changed_files.append(path)
355
else:
356
# New file
357
changed_files.append(path)
358
359
return changed_files
360
361
def clear_file_caches():
362
"""Clear all file-related caches."""
363
364
# Clear file discovery caches
365
cache_keys_to_clear = []
366
367
# You would need to implement cache key pattern matching
368
# This is a simplified example
369
for key in cache._cache.keys():
370
if key.startswith('rosetta_file_'):
371
cache_keys_to_clear.append(key)
372
373
for key in cache_keys_to_clear:
374
cache.delete(key)
375
```