Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/templatetags/i18n.py: 19%

290 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from decimal import Decimal 

2 

3from django.conf import settings 

4from django.template import Library, Node, TemplateSyntaxError, Variable 

5from django.template.base import TokenType, render_value_in_context 

6from django.template.defaulttags import token_kwargs 

7from django.utils import translation 

8from django.utils.safestring import SafeData, mark_safe 

9 

10register = Library() 

11 

12 

13class GetAvailableLanguagesNode(Node): 

14 def __init__(self, variable): 

15 self.variable = variable 

16 

17 def render(self, context): 

18 context[self.variable] = [ 

19 (k, translation.gettext(v)) for k, v in settings.LANGUAGES 

20 ] 

21 return "" 

22 

23 

24class GetLanguageInfoNode(Node): 

25 def __init__(self, lang_code, variable): 

26 self.lang_code = lang_code 

27 self.variable = variable 

28 

29 def render(self, context): 

30 lang_code = self.lang_code.resolve(context) 

31 context[self.variable] = translation.get_language_info(lang_code) 

32 return "" 

33 

34 

35class GetLanguageInfoListNode(Node): 

36 def __init__(self, languages, variable): 

37 self.languages = languages 

38 self.variable = variable 

39 

40 def get_language_info(self, language): 

41 # ``language`` is either a language code string or a sequence 

42 # with the language code as its first item 

43 if len(language[0]) > 1: 

44 return translation.get_language_info(language[0]) 

45 else: 

46 return translation.get_language_info(str(language)) 

47 

48 def render(self, context): 

49 langs = self.languages.resolve(context) 

50 context[self.variable] = [self.get_language_info(lang) for lang in langs] 

51 return "" 

52 

53 

54class GetCurrentLanguageNode(Node): 

55 def __init__(self, variable): 

56 self.variable = variable 

57 

58 def render(self, context): 

59 context[self.variable] = translation.get_language() 

60 return "" 

61 

62 

63class GetCurrentLanguageBidiNode(Node): 

64 def __init__(self, variable): 

65 self.variable = variable 

66 

67 def render(self, context): 

68 context[self.variable] = translation.get_language_bidi() 

69 return "" 

70 

71 

72class TranslateNode(Node): 

73 child_nodelists = () 

74 

75 def __init__(self, filter_expression, noop, asvar=None, message_context=None): 

76 self.noop = noop 

77 self.asvar = asvar 

78 self.message_context = message_context 

79 self.filter_expression = filter_expression 

80 if isinstance(self.filter_expression.var, str): 

81 self.filter_expression.var = Variable("'%s'" % self.filter_expression.var) 

82 

83 def render(self, context): 

84 self.filter_expression.var.translate = not self.noop 

85 if self.message_context: 

86 self.filter_expression.var.message_context = self.message_context.resolve( 

87 context 

88 ) 

89 output = self.filter_expression.resolve(context) 

90 value = render_value_in_context(output, context) 

91 # Restore percent signs. Percent signs in template text are doubled 

92 # so they are not interpreted as string format flags. 

93 is_safe = isinstance(value, SafeData) 

94 value = value.replace("%%", "%") 

95 value = mark_safe(value) if is_safe else value 

96 if self.asvar: 

97 context[self.asvar] = value 

98 return "" 

99 else: 

100 return value 

101 

102 

103class BlockTranslateNode(Node): 

104 def __init__( 

105 self, 

106 extra_context, 

107 singular, 

108 plural=None, 

109 countervar=None, 

110 counter=None, 

111 message_context=None, 

112 trimmed=False, 

113 asvar=None, 

114 tag_name="blocktranslate", 

115 ): 

116 self.extra_context = extra_context 

117 self.singular = singular 

118 self.plural = plural 

119 self.countervar = countervar 

120 self.counter = counter 

121 self.message_context = message_context 

122 self.trimmed = trimmed 

123 self.asvar = asvar 

124 self.tag_name = tag_name 

125 

126 def __repr__(self): 

127 return ( 

128 f"<{self.__class__.__qualname__}: " 

129 f"extra_context={self.extra_context!r} " 

130 f"singular={self.singular!r} plural={self.plural!r}>" 

131 ) 

132 

133 def render_token_list(self, tokens): 

134 result = [] 

135 vars = [] 

136 for token in tokens: 

137 if token.token_type == TokenType.TEXT: 

138 result.append(token.contents.replace("%", "%%")) 

139 elif token.token_type == TokenType.VAR: 

140 result.append("%%(%s)s" % token.contents) 

141 vars.append(token.contents) 

142 msg = "".join(result) 

143 if self.trimmed: 

144 msg = translation.trim_whitespace(msg) 

145 return msg, vars 

146 

147 def render(self, context, nested=False): 

148 if self.message_context: 

149 message_context = self.message_context.resolve(context) 

150 else: 

151 message_context = None 

152 # Update() works like a push(), so corresponding context.pop() is at 

153 # the end of function 

154 context.update( 

155 {var: val.resolve(context) for var, val in self.extra_context.items()} 

156 ) 

157 singular, vars = self.render_token_list(self.singular) 

158 if self.plural and self.countervar and self.counter: 

159 count = self.counter.resolve(context) 

160 if not isinstance(count, (Decimal, float, int)): 

161 raise TemplateSyntaxError( 

162 "%r argument to %r tag must be a number." 

163 % (self.countervar, self.tag_name) 

164 ) 

165 context[self.countervar] = count 

166 plural, plural_vars = self.render_token_list(self.plural) 

167 if message_context: 

168 result = translation.npgettext(message_context, singular, plural, count) 

169 else: 

170 result = translation.ngettext(singular, plural, count) 

171 vars.extend(plural_vars) 

172 else: 

173 if message_context: 

174 result = translation.pgettext(message_context, singular) 

175 else: 

176 result = translation.gettext(singular) 

177 default_value = context.template.engine.string_if_invalid 

178 

179 def render_value(key): 

180 if key in context: 

181 val = context[key] 

182 else: 

183 val = default_value % key if "%s" in default_value else default_value 

184 return render_value_in_context(val, context) 

185 

186 data = {v: render_value(v) for v in vars} 

187 context.pop() 

188 try: 

189 result = result % data 

190 except (KeyError, ValueError): 

191 if nested: 

192 # Either string is malformed, or it's a bug 

193 raise TemplateSyntaxError( 

194 "%r is unable to format string returned by gettext: %r " 

195 "using %r" % (self.tag_name, result, data) 

196 ) 

197 with translation.override(None): 

198 result = self.render(context, nested=True) 

199 if self.asvar: 

200 context[self.asvar] = result 

201 return "" 

202 else: 

203 return result 

204 

205 

206class LanguageNode(Node): 

207 def __init__(self, nodelist, language): 

208 self.nodelist = nodelist 

209 self.language = language 

210 

211 def render(self, context): 

212 with translation.override(self.language.resolve(context)): 

213 output = self.nodelist.render(context) 

214 return output 

215 

216 

217@register.tag("get_available_languages") 

218def do_get_available_languages(parser, token): 

219 """ 

220 Store a list of available languages in the context. 

221 

222 Usage:: 

223 

224 {% get_available_languages as languages %} 

225 {% for language in languages %} 

226 ... 

227 {% endfor %} 

228 

229 This puts settings.LANGUAGES into the named variable. 

230 """ 

231 # token.split_contents() isn't useful here because this tag doesn't accept 

232 # variable as arguments. 

233 args = token.contents.split() 

234 if len(args) != 3 or args[1] != "as": 

235 raise TemplateSyntaxError( 

236 "'get_available_languages' requires 'as variable' (got %r)" % args 

237 ) 

238 return GetAvailableLanguagesNode(args[2]) 

239 

240 

241@register.tag("get_language_info") 

242def do_get_language_info(parser, token): 

243 """ 

244 Store the language information dictionary for the given language code in a 

245 context variable. 

246 

247 Usage:: 

248 

249 {% get_language_info for LANGUAGE_CODE as l %} 

250 {{ l.code }} 

251 {{ l.name }} 

252 {{ l.name_translated }} 

253 {{ l.name_local }} 

254 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 

255 """ 

256 args = token.split_contents() 

257 if len(args) != 5 or args[1] != "for" or args[3] != "as": 

258 raise TemplateSyntaxError( 

259 "'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:]) 

260 ) 

261 return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4]) 

262 

263 

264@register.tag("get_language_info_list") 

265def do_get_language_info_list(parser, token): 

266 """ 

267 Store a list of language information dictionaries for the given language 

268 codes in a context variable. The language codes can be specified either as 

269 a list of strings or a settings.LANGUAGES style list (or any sequence of 

270 sequences whose first items are language codes). 

271 

272 Usage:: 

273 

274 {% get_language_info_list for LANGUAGES as langs %} 

275 {% for l in langs %} 

276 {{ l.code }} 

277 {{ l.name }} 

278 {{ l.name_translated }} 

279 {{ l.name_local }} 

280 {{ l.bidi|yesno:"bi-directional,uni-directional" }} 

281 {% endfor %} 

282 """ 

283 args = token.split_contents() 

284 if len(args) != 5 or args[1] != "for" or args[3] != "as": 

285 raise TemplateSyntaxError( 

286 "'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:]) 

287 ) 

288 return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4]) 

289 

290 

291@register.filter 

292def language_name(lang_code): 

293 return translation.get_language_info(lang_code)["name"] 

294 

295 

296@register.filter 

297def language_name_translated(lang_code): 

298 english_name = translation.get_language_info(lang_code)["name"] 

299 return translation.gettext(english_name) 

300 

301 

302@register.filter 

303def language_name_local(lang_code): 

304 return translation.get_language_info(lang_code)["name_local"] 

305 

306 

307@register.filter 

308def language_bidi(lang_code): 

309 return translation.get_language_info(lang_code)["bidi"] 

310 

311 

312@register.tag("get_current_language") 

313def do_get_current_language(parser, token): 

314 """ 

315 Store the current language in the context. 

316 

317 Usage:: 

318 

319 {% get_current_language as language %} 

320 

321 This fetches the currently active language and puts its value into the 

322 ``language`` context variable. 

323 """ 

324 # token.split_contents() isn't useful here because this tag doesn't accept 

325 # variable as arguments. 

326 args = token.contents.split() 

327 if len(args) != 3 or args[1] != "as": 

328 raise TemplateSyntaxError( 

329 "'get_current_language' requires 'as variable' (got %r)" % args 

330 ) 

331 return GetCurrentLanguageNode(args[2]) 

332 

333 

334@register.tag("get_current_language_bidi") 

335def do_get_current_language_bidi(parser, token): 

336 """ 

337 Store the current language layout in the context. 

338 

339 Usage:: 

340 

341 {% get_current_language_bidi as bidi %} 

342 

343 This fetches the currently active language's layout and puts its value into 

344 the ``bidi`` context variable. True indicates right-to-left layout, 

345 otherwise left-to-right. 

346 """ 

347 # token.split_contents() isn't useful here because this tag doesn't accept 

348 # variable as arguments. 

349 args = token.contents.split() 

350 if len(args) != 3 or args[1] != "as": 

351 raise TemplateSyntaxError( 

352 "'get_current_language_bidi' requires 'as variable' (got %r)" % args 

353 ) 

354 return GetCurrentLanguageBidiNode(args[2]) 

355 

356 

357@register.tag("translate") 

358@register.tag("trans") 

359def do_translate(parser, token): 

360 """ 

361 Mark a string for translation and translate the string for the current 

362 language. 

363 

364 Usage:: 

365 

366 {% translate "this is a test" %} 

367 

368 This marks the string for translation so it will be pulled out by 

369 makemessages into the .po files and runs the string through the translation 

370 engine. 

371 

372 There is a second form:: 

373 

374 {% translate "this is a test" noop %} 

375 

376 This marks the string for translation, but returns the string unchanged. 

377 Use it when you need to store values into forms that should be translated 

378 later on. 

379 

380 You can use variables instead of constant strings 

381 to translate stuff you marked somewhere else:: 

382 

383 {% translate variable %} 

384 

385 This tries to translate the contents of the variable ``variable``. Make 

386 sure that the string in there is something that is in the .po file. 

387 

388 It is possible to store the translated string into a variable:: 

389 

390 {% translate "this is a test" as var %} 

391 {{ var }} 

392 

393 Contextual translations are also supported:: 

394 

395 {% translate "this is a test" context "greeting" %} 

396 

397 This is equivalent to calling pgettext instead of (u)gettext. 

398 """ 

399 bits = token.split_contents() 

400 if len(bits) < 2: 

401 raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0]) 

402 message_string = parser.compile_filter(bits[1]) 

403 remaining = bits[2:] 

404 

405 noop = False 

406 asvar = None 

407 message_context = None 

408 seen = set() 

409 invalid_context = {"as", "noop"} 

410 

411 while remaining: 

412 option = remaining.pop(0) 

413 if option in seen: 

414 raise TemplateSyntaxError( 

415 "The '%s' option was specified more than once." % option, 

416 ) 

417 elif option == "noop": 

418 noop = True 

419 elif option == "context": 

420 try: 

421 value = remaining.pop(0) 

422 except IndexError: 

423 raise TemplateSyntaxError( 

424 "No argument provided to the '%s' tag for the context option." 

425 % bits[0] 

426 ) 

427 if value in invalid_context: 

428 raise TemplateSyntaxError( 

429 "Invalid argument '%s' provided to the '%s' tag for the context " 

430 "option" % (value, bits[0]), 

431 ) 

432 message_context = parser.compile_filter(value) 

433 elif option == "as": 

434 try: 

435 value = remaining.pop(0) 

436 except IndexError: 

437 raise TemplateSyntaxError( 

438 "No argument provided to the '%s' tag for the as option." % bits[0] 

439 ) 

440 asvar = value 

441 else: 

442 raise TemplateSyntaxError( 

443 "Unknown argument for '%s' tag: '%s'. The only options " 

444 "available are 'noop', 'context' \"xxx\", and 'as VAR'." 

445 % ( 

446 bits[0], 

447 option, 

448 ) 

449 ) 

450 seen.add(option) 

451 

452 return TranslateNode(message_string, noop, asvar, message_context) 

453 

454 

455@register.tag("blocktranslate") 

456@register.tag("blocktrans") 

457def do_block_translate(parser, token): 

458 """ 

459 Translate a block of text with parameters. 

460 

461 Usage:: 

462 

463 {% blocktranslate with bar=foo|filter boo=baz|filter %} 

464 This is {{ bar }} and {{ boo }}. 

465 {% endblocktranslate %} 

466 

467 Additionally, this supports pluralization:: 

468 

469 {% blocktranslate count count=var|length %} 

470 There is {{ count }} object. 

471 {% plural %} 

472 There are {{ count }} objects. 

473 {% endblocktranslate %} 

474 

475 This is much like ngettext, only in template syntax. 

476 

477 The "var as value" legacy format is still supported:: 

478 

479 {% blocktranslate with foo|filter as bar and baz|filter as boo %} 

480 {% blocktranslate count var|length as count %} 

481 

482 The translated string can be stored in a variable using `asvar`:: 

483 

484 {% blocktranslate with bar=foo|filter boo=baz|filter asvar var %} 

485 This is {{ bar }} and {{ boo }}. 

486 {% endblocktranslate %} 

487 {{ var }} 

488 

489 Contextual translations are supported:: 

490 

491 {% blocktranslate with bar=foo|filter context "greeting" %} 

492 This is {{ bar }}. 

493 {% endblocktranslate %} 

494 

495 This is equivalent to calling pgettext/npgettext instead of 

496 (u)gettext/(u)ngettext. 

497 """ 

498 bits = token.split_contents() 

499 

500 options = {} 

501 remaining_bits = bits[1:] 

502 asvar = None 

503 while remaining_bits: 

504 option = remaining_bits.pop(0) 

505 if option in options: 

506 raise TemplateSyntaxError( 

507 "The %r option was specified more than once." % option 

508 ) 

509 if option == "with": 

510 value = token_kwargs(remaining_bits, parser, support_legacy=True) 

511 if not value: 

512 raise TemplateSyntaxError( 

513 '"with" in %r tag needs at least one keyword argument.' % bits[0] 

514 ) 

515 elif option == "count": 

516 value = token_kwargs(remaining_bits, parser, support_legacy=True) 

517 if len(value) != 1: 

518 raise TemplateSyntaxError( 

519 '"count" in %r tag expected exactly ' 

520 "one keyword argument." % bits[0] 

521 ) 

522 elif option == "context": 

523 try: 

524 value = remaining_bits.pop(0) 

525 value = parser.compile_filter(value) 

526 except Exception: 

527 raise TemplateSyntaxError( 

528 '"context" in %r tag expected exactly one argument.' % bits[0] 

529 ) 

530 elif option == "trimmed": 

531 value = True 

532 elif option == "asvar": 

533 try: 

534 value = remaining_bits.pop(0) 

535 except IndexError: 

536 raise TemplateSyntaxError( 

537 "No argument provided to the '%s' tag for the asvar option." 

538 % bits[0] 

539 ) 

540 asvar = value 

541 else: 

542 raise TemplateSyntaxError( 

543 "Unknown argument for %r tag: %r." % (bits[0], option) 

544 ) 

545 options[option] = value 

546 

547 if "count" in options: 

548 countervar, counter = next(iter(options["count"].items())) 

549 else: 

550 countervar, counter = None, None 

551 if "context" in options: 

552 message_context = options["context"] 

553 else: 

554 message_context = None 

555 extra_context = options.get("with", {}) 

556 

557 trimmed = options.get("trimmed", False) 

558 

559 singular = [] 

560 plural = [] 

561 while parser.tokens: 

562 token = parser.next_token() 

563 if token.token_type in (TokenType.VAR, TokenType.TEXT): 

564 singular.append(token) 

565 else: 

566 break 

567 if countervar and counter: 

568 if token.contents.strip() != "plural": 

569 raise TemplateSyntaxError( 

570 "%r doesn't allow other block tags inside it" % bits[0] 

571 ) 

572 while parser.tokens: 

573 token = parser.next_token() 

574 if token.token_type in (TokenType.VAR, TokenType.TEXT): 

575 plural.append(token) 

576 else: 

577 break 

578 end_tag_name = "end%s" % bits[0] 

579 if token.contents.strip() != end_tag_name: 

580 raise TemplateSyntaxError( 

581 "%r doesn't allow other block tags (seen %r) inside it" 

582 % (bits[0], token.contents) 

583 ) 

584 

585 return BlockTranslateNode( 

586 extra_context, 

587 singular, 

588 plural, 

589 countervar, 

590 counter, 

591 message_context, 

592 trimmed=trimmed, 

593 asvar=asvar, 

594 tag_name=bits[0], 

595 ) 

596 

597 

598@register.tag 

599def language(parser, token): 

600 """ 

601 Enable the given language just for this block. 

602 

603 Usage:: 

604 

605 {% language "de" %} 

606 This is {{ bar }} and {{ boo }}. 

607 {% endlanguage %} 

608 """ 

609 bits = token.split_contents() 

610 if len(bits) != 2: 

611 raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0]) 

612 language = parser.compile_filter(bits[1]) 

613 nodelist = parser.parse(("endlanguage",)) 

614 parser.delete_first_token() 

615 return LanguageNode(nodelist, language)