Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

import hashlib 

import os 

import os.path 

import re 

try: 

# Python 3 

from urllib.parse import urlencode 

except ImportError: 

# Python 2 

from urllib import urlencode 

import zipfile 

 

from django.conf import settings 

from django.contrib import messages 

from django.contrib.auth.decorators import login_required, user_passes_test 

from django.core.paginator import Paginator 

from django.http import Http404, HttpResponseRedirect, HttpResponse, JsonResponse 

from django.views.decorators.cache import never_cache 

from django.views.generic import TemplateView, View 

try: 

from django.urls import reverse 

except ImportError: 

from django.core.urlresolvers import reverse 

from django.utils.decorators import method_decorator 

from django.utils.functional import cached_property 

from django.utils.translation import ugettext_lazy as _ 

from microsofttranslator import Translator, TranslateApiException 

from polib import pofile 

import six 

import unicodedata 

 

from rosetta import get_version as get_rosetta_version 

from rosetta.access import can_translate, can_translate_language 

from rosetta.conf import settings as rosetta_settings 

from rosetta.poutil import find_pos, pagination_range, timestamp_with_timezone 

from rosetta.signals import entry_changed, post_save 

from rosetta.storage import get_storage 

 

 

def get_app_name(path): 

return path.split('/locale')[0].split('/')[-1] 

 

 

class RosettaBaseMixin(object): 

"""A mixin class for Rosetta's class-based views. It provides: 

* security (see decorated dispatch() method) 

* a property for the 'po_filter' url argument 

""" 

 

# Handle security in our mixin 

# NOTE: after we drop support for Django 1.8, we can employ these decorators 

# more cleanly on the class itself, rather than the dispatch() method. (See 

# the Django docs: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class) 

@method_decorator(never_cache) 

@method_decorator(login_required) 

@method_decorator(user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL)) 

def dispatch(self, *args, **kwargs): 

return super(RosettaBaseMixin, self).dispatch(*args, **kwargs) 

 

@cached_property 

def po_filter(self): 

"""Return the filter applied to all of the .po files under consideration 

to determine which file is currently being translated. Options are: 

'all', 'django', 'third-party', 'project'. 

 

If the filter isn't in this list, throw a 404. 

""" 

po_filter = self.kwargs.get('po_filter') 

69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true if po_filter not in {'all', 'django', 'third-party', 'project'}: 

raise Http404 

return po_filter 

 

 

class RosettaFileLevelMixin(RosettaBaseMixin): 

"""Mixin for dealing with views that work specifically with a single 

.po file. In addition to what the super class brings, it adds the following 

properties: 

* language_id (e.g. 'fr'); derived from url, and validated 

* po_file_path (filesystem path to catalog) 

* po_file (pofile object) 

* po_file_is_writable (bool: do we have filesystem write perms to file) 

""" 

def _request_request(self, key, default=None): 

if key in self.request.GET: 

return self.request.GET.get(key) 

86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true elif key in self.request.POST: 

return self.request.POST.get(key) 

return default 

 

@cached_property 

def language_id(self): 

"""Determine/return the language id from the url kwargs, after 

validating that: 

1. the language is in settings.LANGUAGES, and 

2. the current user is permitted to translate that language 

 

(If either of the above fail, throw a 404.) 

""" 

# (Formerly known as "rosetta_i18n_lang_code") 

lang_id = self.kwargs['lang_id'] 

if lang_id not in {l[0] for l in settings.LANGUAGES}: 

raise Http404 

103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true if not can_translate_language(self.request.user, lang_id): 

raise Http404 

return lang_id 

 

@cached_property 

def po_file_path(self): 

"""Based on the url kwargs, infer and return the path to the .po file to 

be shown/updated. 

 

Throw a 404 if a file isn't found. 

""" 

# This was formerly referred to as 'rosetta_i18n_fn' 

idx = self.kwargs['idx'] 

idx = int(idx) # idx matched url re expression; calling int() is safe 

 

third_party_apps = self.po_filter in ('all', 'third-party') 

django_apps = self.po_filter in ('all', 'django') 

project_apps = self.po_filter in ('all', 'project') 

 

po_paths = find_pos(self.language_id, 

project_apps=project_apps, 

django_apps=django_apps, 

third_party_apps=third_party_apps, 

) 

po_paths.sort(key=get_app_name) 

 

try: 

path = po_paths[idx] 

except IndexError: 

raise Http404 

return path 

 

@cached_property 

def po_file(self): 

"""Return the parsed .po file that is currently being translated/viewed. 

 

(Note that this parsing also involves marking up each entry with a hash 

of its contents.) 

""" 

if self.po_file_is_writable: 

# If we can write changes to file, then we pull it up fresh with 

# each request. 

# XXX: brittle; what if this path doesn't exist? Isn't a .po file? 

po_file = pofile(self.po_file_path, 

wrapwidth=rosetta_settings.POFILE_WRAP_WIDTH) 

for entry in po_file: 

# Entry is an object representing a single entry in the catalog. 

# We interate through the *entire catalog*, pasting a hashed 

# value of the meat of each entry on its side in an attribute 

# called "md5hash". 

str_to_hash = ( 

six.text_type(entry.msgid) + 

six.text_type(entry.msgstr) + 

six.text_type(entry.msgctxt or '') 

).encode('utf8') 

entry.md5hash = hashlib.md5(str_to_hash).hexdigest() 

else: 

storage = get_storage(self.request) 

po_file = storage.get(self.po_file_cache_key, None) 

if not po_file: 

po_file = pofile(self.po_file_path) 

for entry in po_file: 

# Entry is an object representing a single entry in the 

# catalog. We interate through the entire catalog, pasting 

# a hashed value of the meat of each entry on its side in 

# an attribute called "md5hash". 

str_to_hash = ( 

six.text_type(entry.msgid) + 

six.text_type(entry.msgstr) + 

six.text_type(entry.msgctxt or '') 

).encode('utf8') 

entry.md5hash = hashlib.new('md5', str_to_hash).hexdigest() 

storage.set(self.po_file_cache_key, po_file) 

return po_file 

 

@cached_property 

def po_file_cache_key(self): 

"""Return the cache key used to save/access the .po file (when actually 

persisted in cache). 

""" 

return 'po-file-%s' % self.po_file_path 

 

@cached_property 

def po_file_is_writable(self): 

"""Return True if we're able (in terms of file system permissions) to 

write out changes to the .po file we're translating. 

""" 

# (This was formerly called 'rosetta_i18n_write'.) 

return os.access(self.po_file_path, os.W_OK) 

 

 

class TranslationFileListView(RosettaBaseMixin, TemplateView): 

"""Lists the languages, the gettext catalog files that can be translated, 

and their translation progress for a filtered list of apps/projects. 

""" 

http_method_names = ['get'] 

template_name = 'rosetta/file-list.html' 

 

def get_context_data(self, **kwargs): 

context = super(TranslationFileListView, self).get_context_data(**kwargs) 

 

third_party_apps = self.po_filter in ('all', 'third-party') 

django_apps = self.po_filter in ('all', 'django') 

project_apps = self.po_filter in ('all', 'project') 

 

languages = [] 

has_pos = False 

for language in settings.LANGUAGES: 

if not can_translate_language(self.request.user, language[0]): 

continue 

 

po_paths = find_pos(language[0], 

project_apps=project_apps, 

django_apps=django_apps, 

third_party_apps=third_party_apps, 

) 

po_files = [(get_app_name(l), os.path.realpath(l), pofile(l)) for l in po_paths] 

po_files.sort(key=lambda app: app[0]) 

languages.append((language[0], _(language[1]), po_files)) 

has_pos = has_pos or bool(po_paths) 

 

try: 

ADMIN_MEDIA_PREFIX = settings.ADMIN_MEDIA_PREFIX 

except AttributeError: 

ADMIN_MEDIA_PREFIX = settings.STATIC_URL + 'admin/' 

 

context['version'] = get_rosetta_version(True) 

context['ADMIN_MEDIA_PREFIX'] = ADMIN_MEDIA_PREFIX 

context['languages'] = languages 

context['has_pos'] = has_pos 

context['po_filter'] = self.po_filter 

return context 

 

 

class TranslationFormView(RosettaFileLevelMixin, TemplateView): 

"""Show a form with a page's worth of messages to be translated; handle its 

submission by updating cached pofile and, if possible, writing out changes 

to existing .po file. 

 

Query strings that affect what's shown: 

* msg_filter: filters which messages are displayed. One of 'all', 'fuzzy', 

'translated', and 'untranslated' 

* ref_lang: specifies which language should be shown as the source. Only 

applicable when REF_LANG setting is set to True 

* page: which page (number) should be shown of the paginated results (with 

msg_filter or query applied) 

* query: a search string, where only matches are shown. Fields that are 

searched include: source, translated text, "occurence" file path, or 

context hints. 

""" 

# Note: due to the unorthodox nature of the form itself, we're not using 

# Django's generic FormView as our base class. 

http_method_names = ['get', 'post'] 

template_name = 'rosetta/form.html' 

 

def fix_nls(self, in_, out_): 

"""Fixes submitted translations by filtering carriage returns and pairing 

newlines at the begging and end of the translated string with the original 

""" 

262 ↛ 263line 262 didn't jump to line 263, because the condition on line 262 was never true if 0 == len(in_) or 0 == len(out_): 

return out_ 

 

265 ↛ 266line 265 didn't jump to line 266, because the condition on line 265 was never true if "\r" in out_ and "\r" not in in_: 

out_ = out_.replace("\r", '') 

 

if "\n" == in_[0] and "\n" != out_[0]: 

out_ = "\n" + out_ 

270 ↛ 271line 270 didn't jump to line 271, because the condition on line 270 was never true elif "\n" != in_[0] and "\n" == out_[0]: 

out_ = out_.lstrip() 

272 ↛ 273line 272 didn't jump to line 273, because the condition on line 272 was never true if 0 == len(out_): 

pass 

elif "\n" == in_[-1] and "\n" != out_[-1]: 

out_ = out_ + "\n" 

276 ↛ 277line 276 didn't jump to line 277, because the condition on line 276 was never true elif "\n" != in_[-1] and "\n" == out_[-1]: 

out_ = out_.rstrip() 

return out_ 

 

def post(self, request, *args, **kwargs): 

"""The only circumstances when we POST is to submit the main form, both 

updating translations (if any changed) and advancing to the next page of 

messages. 

 

There is no notion of validation of this content; as implemented, unknown 

fields are ignored and a generic failure message is shown. 

 

Submitted changes are saved out to the specified .po file on the 

filesystem if that file is writable, otherwise the cached version of the 

file is updated (so it can be downloaded). Then the user is redirected 

to the next page of messages (if there is one; otherwise they're 

redirected back to the current page). 

""" 

# The message text inputs are captured as hashes of their initial 

# contents, preceded by "m_". Messages with plurals end with their 

# variation number. 

single_text_input_regex = re.compile(r'^m_([0-9a-f]+)$') 

plural_text_input_regex = re.compile(r'^m_([0-9a-f]+)_([0-9]+)$') 

file_change = False 

for field_name, new_msgstr in request.POST.items(): 

md5hash = None 

 

if plural_text_input_regex.match(field_name): 

md5hash, plural_id = plural_text_input_regex.match(field_name).groups() 

md5hash = str(md5hash) 

# polib parses .po files into unicode strings, but 

# doesn't bother to convert plural indexes to int, 

# so we need unicode here. 

plural_id = six.text_type(plural_id) 

 

# Above no longer true as of Polib 1.0.4 

312 ↛ 319line 312 didn't jump to line 319, because the condition on line 312 was never false if plural_id and plural_id.isdigit(): 

plural_id = int(plural_id) 

 

315 ↛ 319line 315 didn't jump to line 319, because the condition on line 315 was never false elif single_text_input_regex.match(field_name): 

md5hash = str(single_text_input_regex.match(field_name).groups()[0]) 

plural_id = None 

 

319 ↛ 300line 319 didn't jump to line 300, because the condition on line 319 was never false if md5hash is not None: # Empty string should be processed! 

entry = self.po_file.find(md5hash, 'md5hash') 

# If someone did a makemessage, some entries might 

# have been removed, so we need to check. 

if entry: 

old_msgstr = entry.msgstr 

if plural_id is not None: # 0 is ok! 

entry.msgstr_plural[plural_id] = self.fix_nls( 

entry.msgid_plural, new_msgstr 

) 

else: 

entry.msgstr = self.fix_nls(entry.msgid, new_msgstr) 

 

is_fuzzy = bool(self.request.POST.get('f_%s' % md5hash, False)) 

old_fuzzy = 'fuzzy' in entry.flags 

 

335 ↛ 336line 335 didn't jump to line 336, because the condition on line 335 was never true if old_fuzzy and not is_fuzzy: 

entry.flags.remove('fuzzy') 

337 ↛ 338line 337 didn't jump to line 338, because the condition on line 337 was never true elif not old_fuzzy and is_fuzzy: 

entry.flags.append('fuzzy') 

 

file_change = True 

 

342 ↛ 300line 342 didn't jump to line 300, because the condition on line 342 was never false if old_msgstr != new_msgstr or old_fuzzy != is_fuzzy: 

entry_changed.send(sender=entry, 

user=request.user, 

old_msgstr=old_msgstr, 

old_fuzzy=old_fuzzy, 

pofile=self.po_file_path, 

language_code=self.language_id, 

) 

else: 

messages.error( 

self.request, 

_("Some items in your last translation block couldn't " 

"be saved: this usually happens when the catalog file " 

"changes on disk after you last loaded it."), 

) 

 

if file_change and self.po_file_is_writable: 

try: 

self.po_file.metadata['Last-Translator'] = unicodedata.normalize( 

'NFKD', u"%s %s <%s>" % ( 

getattr(self.request.user, 'first_name', 'Anonymous'), 

getattr(self.request.user, 'last_name', 'User'), 

getattr(self.request.user, 'email', 'anonymous@user.tld') 

) 

).encode('ascii', 'ignore') 

self.po_file.metadata['X-Translated-Using'] = u"django-rosetta %s" % ( 

get_rosetta_version(False)) 

self.po_file.metadata['PO-Revision-Date'] = timestamp_with_timezone() 

except UnicodeDecodeError: 

pass 

 

try: 

self.po_file.save() 

po_filepath, ext = os.path.splitext(self.po_file_path) 

 

if rosetta_settings.AUTO_COMPILE: 

self.po_file.save_as_mofile(po_filepath + '.mo') 

 

post_save.send(sender=None, language_code=self.language_id, 

request=self.request 

) 

# Try auto-reloading via the WSGI daemon mode reload mechanism 

should_try_wsgi_reload = ( 

rosetta_settings.WSGI_AUTO_RELOAD and 

'mod_wsgi.process_group' in self.request.environ and 

self.request.environ.get('mod_wsgi.process_group', None) and 

'SCRIPT_FILENAME' in self.request.environ and 

int(self.request.environ.get('mod_wsgi.script_reloading', 0)) 

) 

391 ↛ 392line 391 didn't jump to line 392, because the condition on line 391 was never true if should_try_wsgi_reload: 

try: 

os.utime(self.request.environ.get('SCRIPT_FILENAME'), None) 

except OSError: 

pass 

# Try auto-reloading via uwsgi daemon reload mechanism 

397 ↛ 398line 397 didn't jump to line 398, because the condition on line 397 was never true if rosetta_settings.UWSGI_AUTO_RELOAD: 

try: 

import uwsgi 

uwsgi.reload() # pretty easy right? 

except: 

pass # we may not be running under uwsgi :P 

# XXX: It would be nice to add a success message here! 

except Exception as e: 

messages.error(self.request, e) 

 

if file_change and not self.po_file_is_writable: 

storage = get_storage(self.request) 

storage.set(self.po_file_cache_key, self.po_file) 

 

# Reconstitute url to redirect to. Start with determining whether the 

# page number can be incremented. 

paginator = Paginator(self.get_entries(), rosetta_settings.MESSAGES_PER_PAGE) 

try: 

page = int(self._request_request('page', 1)) 

except ValueError: 

page = 1 # fall back to page 1 

else: 

419 ↛ 420line 419 didn't jump to line 420, because the condition on line 419 was never true if not (0 < page <= paginator.num_pages): 

page = 1 

if page < paginator.num_pages: 

page += 1 

query_string_args = { 

'msg_filter': self.msg_filter, 

'query': self.query, 

'ref_lang': self.ref_lang, 

'page': page, 

} 

# Winnow down the query string args to non-blank ones 

query_string_args = {k: v for k, v in query_string_args.items() if v} 

return HttpResponseRedirect("{url}?{qs}".format( 

url=reverse('rosetta-form', kwargs=self.kwargs), 

qs=urlencode(query_string_args), 

)) 

 

def get_context_data(self, **kwargs): 

context = super(TranslationFormView, self).get_context_data(**kwargs) 

entries = self.get_entries() 

paginator = Paginator(entries, rosetta_settings.MESSAGES_PER_PAGE) 

 

# Handle REF_LANG setting; mark up our entries with the reg lang's 

# corresponding translations 

LANGUAGES = list(settings.LANGUAGES) 

444 ↛ 461line 444 didn't jump to line 461, because the condition on line 444 was never false if rosetta_settings.ENABLE_REFLANG: 

if self.ref_lang_po_file: 

for o in paginator.object_list: 

ref_entry = self.ref_lang_po_file.find(o.msgid) 

if ref_entry and ref_entry.msgstr: 

o.ref_txt = ref_entry.msgstr 

else: 

o.ref_txt = o.msgid 

else: 

for o in paginator.object_list: 

o.ref_txt = o.msgid 

# XXX: having "MSGID" at the end of the dropdown is really odd, no? 

# Why not instead do this? 

# LANGUAGES = [('', '----')] + list(settings.LANGUAGES) 

LANGUAGES.append(('msgid', 'MSGID')) 

 

# Determine page number & how pagination links should be displayed 

try: 

page = int(self._request_request('page', 1)) 

except ValueError: 

page = 1 # fall back to page 1 

else: 

if not (0 < page <= paginator.num_pages): 

page = 1 

needs_pagination = paginator.num_pages > 1 

if needs_pagination: 

if paginator.num_pages >= 10: 

page_range = pagination_range(1, paginator.num_pages, page) 

else: 

page_range = range(1, 1 + paginator.num_pages) 

 

rosetta_messages = paginator.page(page).object_list 

 

# Handle MAIN_LANGUAGE setting, if applicable; mark up each entry 

# in the pagination window with the "main language"'s string. 

main_language_id = rosetta_settings.MAIN_LANGUAGE 

main_language = None 

481 ↛ 483line 481 didn't jump to line 483, because the condition on line 481 was never true if main_language_id and main_language_id != self.language_id: 

# Translate from id to language name 

for language in settings.LANGUAGES: 

if language[0] == main_language_id: 

main_language = _(language[1]) 

break 

487 ↛ 488line 487 didn't jump to line 488, because the condition on line 487 was never true if main_language: 

main_lang_po_path = self.po_file_path.replace( 

'/%s/' % self.language_id, 

'/%s/' % main_language_id, 

) 

# XXX: brittle; what if this path doesn't exist? Isn't a .po file? 

main_lang_po = pofile(main_lang_po_path) 

 

for message in rosetta_messages: 

message.main_lang = main_lang_po.find(message.msgid).msgstr 

 

# Collect some constants for the template 

try: 

ADMIN_MEDIA_PREFIX = settings.ADMIN_MEDIA_PREFIX 

ADMIN_IMAGE_DIR = ADMIN_MEDIA_PREFIX + 'img/admin/' 

except AttributeError: 

ADMIN_MEDIA_PREFIX = settings.STATIC_URL + 'admin/' 

ADMIN_IMAGE_DIR = ADMIN_MEDIA_PREFIX + 'img/' 

rosetta_i18n_lang_name = six.text_type( 

dict(settings.LANGUAGES).get(self.language_id) 

) 

# "bidi" as in "bi-directional" 

rosetta_i18n_lang_bidi = self.language_id.split('-')[0] in settings.LANGUAGES_BIDI 

query_string_args = {} 

if self.msg_filter: 

query_string_args['msg_filter'] = self.msg_filter 

if self.query: 

query_string_args['query'] = self.query 

515 ↛ 518line 515 didn't jump to line 518, because the condition on line 515 was never false if self.ref_lang: 

query_string_args['ref_lang'] = self.ref_lang 

# Base for pagination links; the page num itself is added in template 

pagination_query_string_base = urlencode(query_string_args) 

# Base for msg filter links; it doesn't make sense to persist page 

# numbers in these links. We just pass in ref_lang, if it's set. 

filter_query_string_base = urlencode( 

{k: v for k, v in query_string_args.items() if k == 'ref_lang'} 

) 

 

context.update({ 

'version': get_rosetta_version(True), 

'ADMIN_MEDIA_PREFIX': ADMIN_MEDIA_PREFIX, 

'ADMIN_IMAGE_DIR': ADMIN_IMAGE_DIR, 

'LANGUAGES': LANGUAGES, 

'rosetta_settings': rosetta_settings, 

'rosetta_i18n_lang_name': rosetta_i18n_lang_name, 

'rosetta_i18n_lang_code': self.language_id, 

'rosetta_i18n_lang_bidi': rosetta_i18n_lang_bidi, 

'rosetta_i18n_filter': self.msg_filter, 

'rosetta_i18n_write': self.po_file_is_writable, 

'rosetta_messages': rosetta_messages, 

'page_range': needs_pagination and page_range, 

'needs_pagination': needs_pagination, 

'main_language': main_language, 

'rosetta_i18n_app': get_app_name(self.po_file_path), 

'page': page, 

'query': self.query, 

'pagination_query_string_base': pagination_query_string_base, 

'filter_query_string_base': filter_query_string_base, 

'paginator': paginator, 

'rosetta_i18n_pofile': self.po_file, 

'ref_lang': self.ref_lang, 

}) 

 

return context 

 

@cached_property 

def ref_lang(self): 

"""Return the language id for the "reference language" (the language to 

be translated *from*, if not English). 

 

Throw a 404 if it's not in settings.LANGUAGES. 

""" 

ref_lang = self._request_request('ref_lang', 'msgid') 

if ref_lang != 'msgid': 

allowed_languages = {l[0] for l in settings.LANGUAGES} 

562 ↛ 563line 562 didn't jump to line 563, because the condition on line 562 was never true if ref_lang not in allowed_languages: 

raise Http404 

return ref_lang 

 

@cached_property 

def ref_lang_po_file(self): 

"""Return a parsed .po file object for the "reference language", if one 

exists, otherwise None. 

""" 

ref_pofile = None 

if rosetta_settings.ENABLE_REFLANG and self.ref_lang != 'msgid': 

ref_fn = re.sub( 

'/locale/[a-z]{2}/', 

'/locale/%s/' % (self.ref_lang), 

self.po_file_path, 

) 

try: 

ref_pofile = pofile(ref_fn) 

except IOError: 

# there's a syntax error in the PO file and polib can't 

# open it. Let's just do nothing and thus display msgids. 

# XXX: :-/ 

pass 

return ref_pofile 

 

@cached_property 

def msg_filter(self): 

"""Validate/return msg_filter from request (e.g. 'fuzzy', 'untranslated'), 

or a default. 

 

If a query is also specified in the request, then return None. 

""" 

if self.query: 

msg_filter = None 

else: 

msg_filter = self._request_request('msg_filter', 'all') 

available_msg_filters = {'untranslated', 'translated', 'fuzzy', 'all'} 

599 ↛ 600line 599 didn't jump to line 600, because the condition on line 599 was never true if msg_filter not in available_msg_filters: 

msg_filter = 'all' 

return msg_filter 

 

@cached_property 

def query(self): 

"""Strip and return the query (for searching the catalog) from the 

request, or None. 

""" 

return self._request_request('query', '').strip() or None 

 

def get_entries(self): 

"""Return a list of the entries (messages) that would be part of the 

current "view"; that is, all of the ones from this .po file matching the 

current query or msg_filter. 

""" 

if self.query: 

# Scenario #1: terms matching a search query 

rx = re.compile(re.escape(self.query), re.IGNORECASE) 

 

def concat_entry(e): 

return (six.text_type(e.msgstr) + 

six.text_type(e.msgid) + 

six.text_type(e.comment) + 

u''.join([o[0] for o in e.occurrences]) 

) 

 

entries = [e_ for e_ in self.po_file 

if not e_.obsolete and rx.search(concat_entry(e_))] 

else: 

# Scenario #2: filtered list of messages 

if self.msg_filter == 'untranslated': 

entries = self.po_file.untranslated_entries() 

elif self.msg_filter == 'translated': 

entries = self.po_file.translated_entries() 

634 ↛ 635line 634 didn't jump to line 635, because the condition on line 634 was never true elif self.msg_filter == 'fuzzy': 

entries = [e_ for e_ in self.po_file.fuzzy_entries() 

if not e_.obsolete] 

else: 

# ("all") 

entries = [e_ for e_ in self.po_file if not e_.obsolete] 

return entries 

 

 

class TranslationFileDownload(RosettaFileLevelMixin, View): 

"""Download a zip file for a specific catalog including both the raw (.po) 

and compiled (.mo) files, either as they exist on disk, or, if what's on 

disk is unwritable (permissions-wise), return what's in the cache. 

""" 

http_method_names = [u'get'] 

 

def get(self, request, *args, **kwargs): 

try: 

652 ↛ 655line 652 didn't jump to line 655, because the condition on line 652 was never false if len(self.po_file_path.split('/')) >= 5: 

offered_fn = '_'.join(self.po_file_path.split('/')[-5:]) 

else: 

offered_fn = self.po_file_path.split('/')[-1] 

po_fn = str(self.po_file_path.split('/')[-1]) 

mo_fn = str(po_fn.replace('.po', '.mo')) # not so smart, huh 

zipdata = six.BytesIO() 

with zipfile.ZipFile(zipdata, mode="w") as zipf: 

zipf.writestr(po_fn, six.text_type(self.po_file).encode("utf8")) 

zipf.writestr(mo_fn, self.po_file.to_binary()) 

zipdata.seek(0) 

 

response = HttpResponse(zipdata.read()) 

filename = 'filename=%s.%s.zip' % (offered_fn, self.language_id) 

response['Content-Disposition'] = 'attachment; %s' % filename 

response['Content-Type'] = 'application/x-zip' 

return response 

except Exception: 

# XXX: should add a message! 

return HttpResponseRedirect( 

reverse('rosetta-file-list', kwargs={'po_filter': 'project'}) 

) 

 

 

676 ↛ exitline 676 didn't run the lambda on line 676@user_passes_test(lambda user: can_translate(user), settings.LOGIN_URL) 

def translate_text(request): 

language_from = request.GET.get('from', None) 

language_to = request.GET.get('to', None) 

text = request.GET.get('text', None) 

 

if language_from == language_to: 

data = {'success': True, 'translation': text} 

else: 

# run the translation: 

 

AZURE_CLIENT_ID = getattr(settings, 'AZURE_CLIENT_ID', None) 

AZURE_CLIENT_SECRET = getattr(settings, 'AZURE_CLIENT_SECRET', None) 

 

translator = Translator(AZURE_CLIENT_ID, AZURE_CLIENT_SECRET) 

 

try: 

translated_text = translator.translate(text, language_to, language_from) 

data = {'success': True, 'translation': translated_text} 

except TranslateApiException as e: 

data = { 

'success': False, 

'error': "Translation API Exception: {0}".format(e.message), 

} 

 

return JsonResponse(data)