Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/templatetags/i18n.py: 21%

291 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

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, SafeString, 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.is_var = True 

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

83 

84 def render(self, context): 

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

86 if self.message_context: 

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

88 context 

89 ) 

90 output = self.filter_expression.resolve(context) 

91 value = render_value_in_context(output, context) 

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

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

94 is_safe = isinstance(value, SafeData) 

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

96 value = mark_safe(value) if is_safe else value 

97 if self.asvar: 

98 context[self.asvar] = value 

99 return "" 

100 else: 

101 return value 

102 

103 

104class BlockTranslateNode(Node): 

105 def __init__( 

106 self, 

107 extra_context, 

108 singular, 

109 plural=None, 

110 countervar=None, 

111 counter=None, 

112 message_context=None, 

113 trimmed=False, 

114 asvar=None, 

115 tag_name="blocktranslate", 

116 ): 

117 self.extra_context = extra_context 

118 self.singular = singular 

119 self.plural = plural 

120 self.countervar = countervar 

121 self.counter = counter 

122 self.message_context = message_context 

123 self.trimmed = trimmed 

124 self.asvar = asvar 

125 self.tag_name = tag_name 

126 

127 def __repr__(self): 

128 return ( 

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

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

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

132 ) 

133 

134 def render_token_list(self, tokens): 

135 result = [] 

136 vars = [] 

137 for token in tokens: 

138 if token.token_type == TokenType.TEXT: 

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

140 elif token.token_type == TokenType.VAR: 

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

142 vars.append(token.contents) 

143 msg = "".join(result) 

144 if self.trimmed: 

145 msg = translation.trim_whitespace(msg) 

146 return msg, vars 

147 

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

149 if self.message_context: 

150 message_context = self.message_context.resolve(context) 

151 else: 

152 message_context = None 

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

154 # the end of function 

155 context.update( 

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

157 ) 

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

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

160 count = self.counter.resolve(context) 

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

162 raise TemplateSyntaxError( 

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

164 % (self.countervar, self.tag_name) 

165 ) 

166 context[self.countervar] = count 

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

168 if message_context: 

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

170 else: 

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

172 vars.extend(plural_vars) 

173 else: 

174 if message_context: 

175 result = translation.pgettext(message_context, singular) 

176 else: 

177 result = translation.gettext(singular) 

178 default_value = context.template.engine.string_if_invalid 

179 

180 def render_value(key): 

181 if key in context: 

182 val = context[key] 

183 else: 

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

185 return render_value_in_context(val, context) 

186 

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

188 context.pop() 

189 try: 

190 result %= data 

191 except (KeyError, ValueError): 

192 if nested: 

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

194 raise TemplateSyntaxError( 

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

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

197 ) 

198 with translation.override(None): 

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

200 if self.asvar: 

201 context[self.asvar] = SafeString(result) 

202 return "" 

203 else: 

204 return result 

205 

206 

207class LanguageNode(Node): 

208 def __init__(self, nodelist, language): 

209 self.nodelist = nodelist 

210 self.language = language 

211 

212 def render(self, context): 

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

214 output = self.nodelist.render(context) 

215 return output 

216 

217 

218@register.tag("get_available_languages") 

219def do_get_available_languages(parser, token): 

220 """ 

221 Store a list of available languages in the context. 

222 

223 Usage:: 

224 

225 {% get_available_languages as languages %} 

226 {% for language in languages %} 

227 ... 

228 {% endfor %} 

229 

230 This puts settings.LANGUAGES into the named variable. 

231 """ 

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

233 # variable as arguments. 

234 args = token.contents.split() 

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

236 raise TemplateSyntaxError( 

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

238 ) 

239 return GetAvailableLanguagesNode(args[2]) 

240 

241 

242@register.tag("get_language_info") 

243def do_get_language_info(parser, token): 

244 """ 

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

246 context variable. 

247 

248 Usage:: 

249 

250 {% get_language_info for LANGUAGE_CODE as l %} 

251 {{ l.code }} 

252 {{ l.name }} 

253 {{ l.name_translated }} 

254 {{ l.name_local }} 

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

256 """ 

257 args = token.split_contents() 

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

259 raise TemplateSyntaxError( 

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

261 ) 

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

263 

264 

265@register.tag("get_language_info_list") 

266def do_get_language_info_list(parser, token): 

267 """ 

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

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

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

271 sequences whose first items are language codes). 

272 

273 Usage:: 

274 

275 {% get_language_info_list for LANGUAGES as langs %} 

276 {% for l in langs %} 

277 {{ l.code }} 

278 {{ l.name }} 

279 {{ l.name_translated }} 

280 {{ l.name_local }} 

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

282 {% endfor %} 

283 """ 

284 args = token.split_contents() 

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

286 raise TemplateSyntaxError( 

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

288 ) 

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

290 

291 

292@register.filter 

293def language_name(lang_code): 

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

295 

296 

297@register.filter 

298def language_name_translated(lang_code): 

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

300 return translation.gettext(english_name) 

301 

302 

303@register.filter 

304def language_name_local(lang_code): 

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

306 

307 

308@register.filter 

309def language_bidi(lang_code): 

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

311 

312 

313@register.tag("get_current_language") 

314def do_get_current_language(parser, token): 

315 """ 

316 Store the current language in the context. 

317 

318 Usage:: 

319 

320 {% get_current_language as language %} 

321 

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

323 ``language`` context variable. 

324 """ 

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

326 # variable as arguments. 

327 args = token.contents.split() 

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

329 raise TemplateSyntaxError( 

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

331 ) 

332 return GetCurrentLanguageNode(args[2]) 

333 

334 

335@register.tag("get_current_language_bidi") 

336def do_get_current_language_bidi(parser, token): 

337 """ 

338 Store the current language layout in the context. 

339 

340 Usage:: 

341 

342 {% get_current_language_bidi as bidi %} 

343 

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

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

346 otherwise left-to-right. 

347 """ 

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

349 # variable as arguments. 

350 args = token.contents.split() 

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

352 raise TemplateSyntaxError( 

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

354 ) 

355 return GetCurrentLanguageBidiNode(args[2]) 

356 

357 

358@register.tag("translate") 

359@register.tag("trans") 

360def do_translate(parser, token): 

361 """ 

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

363 language. 

364 

365 Usage:: 

366 

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

368 

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

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

371 engine. 

372 

373 There is a second form:: 

374 

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

376 

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

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

379 later on. 

380 

381 You can use variables instead of constant strings 

382 to translate stuff you marked somewhere else:: 

383 

384 {% translate variable %} 

385 

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

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

388 

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

390 

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

392 {{ var }} 

393 

394 Contextual translations are also supported:: 

395 

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

397 

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

399 """ 

400 bits = token.split_contents() 

401 if len(bits) < 2: 

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

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

404 remaining = bits[2:] 

405 

406 noop = False 

407 asvar = None 

408 message_context = None 

409 seen = set() 

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

411 

412 while remaining: 

413 option = remaining.pop(0) 

414 if option in seen: 

415 raise TemplateSyntaxError( 

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

417 ) 

418 elif option == "noop": 

419 noop = True 

420 elif option == "context": 

421 try: 

422 value = remaining.pop(0) 

423 except IndexError: 

424 raise TemplateSyntaxError( 

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

426 % bits[0] 

427 ) 

428 if value in invalid_context: 

429 raise TemplateSyntaxError( 

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

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

432 ) 

433 message_context = parser.compile_filter(value) 

434 elif option == "as": 

435 try: 

436 value = remaining.pop(0) 

437 except IndexError: 

438 raise TemplateSyntaxError( 

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

440 ) 

441 asvar = value 

442 else: 

443 raise TemplateSyntaxError( 

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

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

446 % ( 

447 bits[0], 

448 option, 

449 ) 

450 ) 

451 seen.add(option) 

452 

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

454 

455 

456@register.tag("blocktranslate") 

457@register.tag("blocktrans") 

458def do_block_translate(parser, token): 

459 """ 

460 Translate a block of text with parameters. 

461 

462 Usage:: 

463 

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

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

466 {% endblocktranslate %} 

467 

468 Additionally, this supports pluralization:: 

469 

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

471 There is {{ count }} object. 

472 {% plural %} 

473 There are {{ count }} objects. 

474 {% endblocktranslate %} 

475 

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

477 

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

479 

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

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

482 

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

484 

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

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

487 {% endblocktranslate %} 

488 {{ var }} 

489 

490 Contextual translations are supported:: 

491 

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

493 This is {{ bar }}. 

494 {% endblocktranslate %} 

495 

496 This is equivalent to calling pgettext/npgettext instead of 

497 (u)gettext/(u)ngettext. 

498 """ 

499 bits = token.split_contents() 

500 

501 options = {} 

502 remaining_bits = bits[1:] 

503 asvar = None 

504 while remaining_bits: 

505 option = remaining_bits.pop(0) 

506 if option in options: 

507 raise TemplateSyntaxError( 

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

509 ) 

510 if option == "with": 

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

512 if not value: 

513 raise TemplateSyntaxError( 

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

515 ) 

516 elif option == "count": 

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

518 if len(value) != 1: 

519 raise TemplateSyntaxError( 

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

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

522 ) 

523 elif option == "context": 

524 try: 

525 value = remaining_bits.pop(0) 

526 value = parser.compile_filter(value) 

527 except Exception: 

528 raise TemplateSyntaxError( 

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

530 ) 

531 elif option == "trimmed": 

532 value = True 

533 elif option == "asvar": 

534 try: 

535 value = remaining_bits.pop(0) 

536 except IndexError: 

537 raise TemplateSyntaxError( 

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

539 % bits[0] 

540 ) 

541 asvar = value 

542 else: 

543 raise TemplateSyntaxError( 

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

545 ) 

546 options[option] = value 

547 

548 if "count" in options: 

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

550 else: 

551 countervar, counter = None, None 

552 if "context" in options: 

553 message_context = options["context"] 

554 else: 

555 message_context = None 

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

557 

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

559 

560 singular = [] 

561 plural = [] 

562 while parser.tokens: 

563 token = parser.next_token() 

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

565 singular.append(token) 

566 else: 

567 break 

568 if countervar and counter: 

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

570 raise TemplateSyntaxError( 

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

572 ) 

573 while parser.tokens: 

574 token = parser.next_token() 

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

576 plural.append(token) 

577 else: 

578 break 

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

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

581 raise TemplateSyntaxError( 

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

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

584 ) 

585 

586 return BlockTranslateNode( 

587 extra_context, 

588 singular, 

589 plural, 

590 countervar, 

591 counter, 

592 message_context, 

593 trimmed=trimmed, 

594 asvar=asvar, 

595 tag_name=bits[0], 

596 ) 

597 

598 

599@register.tag 

600def language(parser, token): 

601 """ 

602 Enable the given language just for this block. 

603 

604 Usage:: 

605 

606 {% language "de" %} 

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

608 {% endlanguage %} 

609 """ 

610 bits = token.split_contents() 

611 if len(bits) != 2: 

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

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

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

615 parser.delete_first_token() 

616 return LanguageNode(nodelist, language)