or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

access-control.mdconfiguration.mddjango-signals.mdfile-operations.mdindex.mdstorage-backends.mdtemplate-integration.mdtranslation-services.mdweb-interface.md

template-integration.mddocs/

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

```