Ticket #129: all_patches_bundle.patch
| File all_patches_bundle.patch, 18.4 KB (added by palgarvio, 15 years ago) |
|---|
-
genshi/filters/i18n.py
17 17 """ 18 18 19 19 from compiler import ast 20 from gettext import gettext20 from gettext import NullTranslations 21 21 import re 22 22 23 23 from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ 24 24 END_NS, XML_NAMESPACE, _ensure 25 from genshi.template.base import Template, EXPR, SUB 25 from genshi.template.base import Template, EXPR, SUB, INCLUDE 26 26 from genshi.template.markup import MarkupTemplate, EXEC 27 27 28 28 __all__ = ['Translator', 'extract'] … … 35 35 """Can extract and translate localizable strings from markup streams and 36 36 templates. 37 37 38 For example, assume the follow ng template:38 For example, assume the following template: 39 39 40 40 >>> from genshi.template import MarkupTemplate 41 41 >>> … … 91 91 INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby', 92 92 'summary', 'title']) 93 93 94 def __init__(self, translat e=gettext, ignore_tags=IGNORE_TAGS,94 def __init__(self, translator=NullTranslations(), ignore_tags=IGNORE_TAGS, 95 95 include_attrs=INCLUDE_ATTRS, extract_text=True): 96 96 """Initialize the translator. 97 97 … … 103 103 extracted, or only text in explicit ``gettext`` 104 104 function calls 105 105 """ 106 self.translat e = translate106 self.translator = translator 107 107 self.ignore_tags = ignore_tags 108 108 self.include_attrs = include_attrs 109 109 self.extract_text = extract_text 110 self.i18n_domains = [] 110 111 111 112 def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None): 112 113 """Translate any localizable strings in the given stream. … … 126 127 """ 127 128 ignore_tags = self.ignore_tags 128 129 include_attrs = self.include_attrs 129 translate = self.translate 130 ugettext = self.translator.ugettext 131 ungettext = self.translator.ungettext 132 try: 133 dugettext = self.translator.dugettext 134 dungettext = self.translator.dungettext 135 except AttributeError: 136 # No domain support, show a warning??? 137 dugettext = lambda d, s: ugettext(s) 138 dungettext = lambda d, s, p, n: ungettext(s, p, n) 139 130 140 if not self.extract_text: 131 141 search_text = False 132 142 skip = 0 133 143 i18n_msg = I18N_NAMESPACE['msg'] 144 i18n_choose = I18N_NAMESPACE['choose'] 145 i18n_domain = I18N_NAMESPACE['domain'] 134 146 ns_prefixes = [] 135 147 xml_lang = XML_NAMESPACE['lang'] 136 148 … … 148 160 # handle different events that can be localized 149 161 if kind is START: 150 162 tag, attrs = data 163 164 if i18n_domain in attrs: 165 self.i18n_domains.append( 166 (tag, attrs.get(i18n_domain).strip()) 167 ) 168 attrs -= i18n_domain 169 151 170 if tag in self.ignore_tags or \ 152 171 isinstance(attrs.get(xml_lang), basestring): 153 172 skip += 1 154 173 yield kind, data, pos 155 174 continue 156 175 157 176 new_attrs = [] 158 177 changed = False 159 178 for name, value in attrs: 160 179 newval = value 161 180 if search_text and isinstance(value, basestring): 162 181 if name in include_attrs: 163 newval = self.translate(value) 182 if self.i18n_domains: 183 newval = dugettext(self.i18n_domains[-1][1], 184 value) 185 else: 186 newval = ugettext(value) 164 187 else: 165 188 newval = list(self(_ensure(value), ctxt, 166 search_text=False) 167 ) 189 search_text=False)) 168 190 if newval != value: 169 191 value = newval 170 192 changed = True … … 177 199 continue 178 200 elif i18n_msg in attrs: 179 201 params = attrs.get(i18n_msg) 180 if params and type(params) is list: # event tuple181 params = params[0][1]182 202 msgbuf = MessageBuffer(params) 183 203 attrs -= i18n_msg 204 elif i18n_choose in attrs: 205 params = attrs.get(i18n_choose) 206 msgbuf = MessageBuffer(params) 207 attrs -= i18n_choose 184 208 185 209 yield kind, (tag, attrs), pos 186 210 … … 188 212 if not msgbuf: 189 213 text = data.strip() 190 214 if text: 191 data = data.replace(text, unicode(translate(text))) 215 if self.i18n_domains: 216 data = data.replace(text, 217 unicode(dugettext(self.i18n_domains[-1][1], 218 text))) 219 else: 220 data = data.replace(text, unicode(ugettext(text))) 192 221 yield kind, data, pos 193 222 else: 194 223 msgbuf.append(kind, data, pos) … … 199 228 elif not skip and msgbuf and kind is END: 200 229 msgbuf.append(kind, data, pos) 201 230 if not msgbuf.depth: 202 for event in msgbuf.translate(translate(msgbuf.format())): 231 if msgbuf.singular or msgbuf.plural: 232 singular, plural, expr = msgbuf.format() 233 if self.i18n_domains: 234 events = dungettext(self.i18n_domains[-1][1], 235 singular, plural, 236 expr.evaluate(ctxt)) 237 else: 238 events = ungettext(singular, plural, 239 expr.evaluate(ctxt)) 240 else: 241 if self.i18n_domains: 242 events = dugettext(self.i18n_domains[-1][1], 243 msgbuf.format()) 244 else: 245 events = ugettext(msgbuf.format()) 246 for event in msgbuf.translate(events): 203 247 yield event 204 248 msgbuf = None 205 249 yield kind, data, pos 206 250 207 251 elif kind is SUB: 208 252 subkind, substream = data 209 253 new_substream = list(self(substream, ctxt, msgbuf=msgbuf)) … … 214 258 215 259 elif kind is END_NS and data in ns_prefixes: 216 260 ns_prefixes.remove(data) 217 218 261 else: 219 262 yield kind, data, pos 263 264 if kind is END and self.i18n_domains and \ 265 self.i18n_domains[-1][1] == data: 266 self.i18n_domains.pop() 220 267 221 268 GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext', 'dgettext', 'dngettext', 222 269 'ugettext', 'ungettext') … … 270 317 search_text = False 271 318 skip = 0 272 319 i18n_msg = I18N_NAMESPACE['msg'] 320 i18n_comment = I18N_NAMESPACE['comment'] 321 i18n_choose = I18N_NAMESPACE['choose'] 273 322 xml_lang = XML_NAMESPACE['lang'] 274 323 275 324 for kind, data, pos in stream: … … 293 342 if name in self.include_attrs: 294 343 text = value.strip() 295 344 if text: 296 yield pos[1], None, text 345 yield pos[1], None, text, [] 297 346 else: 298 for lineno, funcname, text in self.extract(347 for lineno, funcname, text, comments in self.extract( 299 348 _ensure(value), gettext_functions, 300 349 search_text=False): 301 yield lineno, funcname, text 350 yield lineno, funcname, text, comments 302 351 303 352 if msgbuf: 304 353 msgbuf.append(kind, data, pos) 305 354 elif i18n_msg in attrs: 306 355 params = attrs.get(i18n_msg) 307 if params and type(params) is list: # event tuple308 params = params[0][1]309 356 msgbuf = MessageBuffer(params, pos[1]) 357 elif i18n_choose in attrs: 358 params = attrs.get(i18n_choose) 359 msgbuf = MessageBuffer(params, pos[1]) 360 if i18n_comment in attrs and msgbuf: 361 msgbuf.comments.append(attrs.get(i18n_comment)) 310 362 311 363 elif not skip and search_text and kind is TEXT: 312 364 if not msgbuf: 313 365 text = data.strip() 314 366 if text and filter(None, [ch.isalpha() for ch in text]): 315 yield pos[1], None, text 367 yield pos[1], None, text, [] 316 368 else: 317 369 msgbuf.append(kind, data, pos) 318 370 319 371 elif not skip and msgbuf and kind is END: 320 372 msgbuf.append(kind, data, pos) 321 373 if not msgbuf.depth: 322 yield msgbuf.lineno, None, msgbuf.format() 374 if msgbuf.singular or msgbuf.plural: 375 singular, plural, num = msgbuf.format() 376 yield msgbuf.lineno, 'ngettext', (singular, plural), \ 377 msgbuf.comments 378 else: 379 yield msgbuf.lineno, None, msgbuf.format(), \ 380 msgbuf.comments 323 381 msgbuf = None 324 382 325 383 elif kind is EXPR or kind is EXEC: … … 327 385 msgbuf.append(kind, data, pos) 328 386 for funcname, strings in extract_from_code(data, 329 387 gettext_functions): 330 yield pos[1], funcname, strings 388 yield pos[1], funcname, strings, [] 331 389 332 390 elif kind is SUB: 333 391 subkind, substream = data 334 392 messages = self.extract(substream, gettext_functions, 335 393 search_text=search_text and not skip, 336 394 msgbuf=msgbuf) 337 for lineno, funcname, text in messages: 338 yield lineno, funcname, text 339 395 for lineno, funcname, text, comments in messages: 396 yield lineno, funcname, text, comments 340 397 398 341 399 class MessageBuffer(object): 342 400 """Helper class for managing internationalized mixed content. 343 401 … … 352 410 :param lineno: the line number on which the first stream event 353 411 belonging to the message was found 354 412 """ 355 self.params = [name.strip() for name in params.split(',')] 413 if isinstance(params, (list, tuple)): 414 self.params = [] 415 for entry in params: 416 if entry[0] == 'TEXT': 417 for param in entry[1].split(','): 418 if param: 419 self.params.append(param.strip()) 420 elif entry[0] == 'EXPR': 421 self.choose_numeral = entry[1] 422 elif isinstance(params, basestring): 423 self.params = [p.strip() for p in params.split(',')] 424 else: 425 self.params = params 426 self.singular_params = self.params[:] 427 self.plural_params = self.params[:] 356 428 self.lineno = lineno 357 429 self.string = [] 430 self.singular = [] 431 self.plural = [] 358 432 self.events = {} 359 433 self.values = {} 360 434 self.depth = 1 361 435 self.order = 1 436 self.choose_order = 1 362 437 self.stack = [0] 438 self.comments = [] 439 self.i18n_choose_singular = I18N_NAMESPACE['singular'] 440 self.i18n_choose_plural = I18N_NAMESPACE['plural'] 441 self.choose_singular = False 442 self.choose_plural = False 363 443 364 444 def append(self, kind, data, pos): 365 445 """Append a stream event to the buffer. … … 369 449 :param pos: the position of the event in the source 370 450 """ 371 451 if kind is TEXT: 372 self.string.append(data) 452 if self.choose_singular: 453 self.singular.append(data) 454 if self.choose_plural: 455 self.plural.append(data) 456 else: 457 self.string.append(data) 373 458 self.events.setdefault(self.stack[-1], []).append(None) 374 459 elif kind is EXPR: 375 param = self.params.pop(0) 376 self.string.append('%%(%s)s' % param) 377 self.events.setdefault(self.stack[-1], []).append(None) 460 if self.choose_singular: 461 param = self.singular_params.pop(0) 462 self.singular.append('%%(%s)s' % param) 463 elif self.choose_plural: 464 param = self.plural_params.pop(0) 465 self.plural.append('%%(%s)s' % param) 466 else: 467 param = self.params.pop(0) 468 self.string.append('%%(%s)s' % param) 378 469 self.values[param] = (kind, data, pos) 470 self.events.setdefault(self.stack[-1], []).append(None) 379 471 else: 380 472 if kind is START: 381 self.string.append(u'[%d:' % self.order) 382 self.events.setdefault(self.order, []).append((kind, data, pos)) 383 self.stack.append(self.order) 473 if self.choose_singular: 474 self.singular.append(u'[%d:' % self.choose_order) 475 self.events.setdefault(self.choose_order, 476 []).append((kind, data, pos)) 477 self.stack.append(self.choose_order) 478 elif self.choose_plural: 479 self.plural.append(u'[%d:' % self.choose_order) 480 self.events.setdefault(self.choose_order, 481 []).append((kind, data, pos)) 482 self.stack.append(self.choose_order) 483 self.choose_order += 1 484 elif self.i18n_choose_singular in data[1]: 485 self.choose_singular = True 486 self.choose_plural = False 487 elif self.i18n_choose_plural in data[1]: 488 self.choose_plural = True 489 self.choose_singular = False 490 else: 491 self.string.append(u'[%d:' % self.order) 492 self.events.setdefault(self.order, 493 []).append((kind, data, pos)) 494 self.stack.append(self.order) 495 self.order += 1 384 496 self.depth += 1 385 self.order += 1386 497 elif kind is END: 387 498 self.depth -= 1 388 499 if self.depth: 389 self.events[self.stack[-1]].append((kind, data, pos)) 390 self.string.append(u']') 391 self.stack.pop() 500 if self.choose_singular: 501 self.choose_singular = False 502 self.events[self.stack[-1]].append((kind, data, pos)) 503 if self.choose_order > 1: 504 self.singular.append(u']') 505 self.stack.pop() 506 elif self.choose_plural: 507 self.events[self.stack[-1]].append((kind, data, pos)) 508 self.choose_plural = False 509 if self.choose_order > 1: 510 self.plural.append(u']') 511 self.stack.pop() 512 else: 513 self.string.append(u']') 514 self.events[self.stack[-1]].append((kind, data, pos)) 515 self.stack.pop() 392 516 393 517 def format(self): 394 518 """Return a message identifier representing the content in the 395 519 buffer. 396 520 """ 521 if self.singular or self.plural: 522 return (u''.join(self.singular).strip(), 523 u''.join(self.plural).strip(), self.choose_numeral) 397 524 return u''.join(self.string).strip() 398 525 399 526 def translate(self, string, regex=re.compile(r'%\((\w+)\)s')): … … 545 672 tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None), 546 673 encoding=encoding) 547 674 translator = Translator(None, ignore_tags, include_attrs, extract_text) 548 for lineno, func, message in translator.extract(tmpl.stream,549 gettext_functions=keywords):550 yield lineno, func, message, []675 for lineno, func, message, comments in translator.extract( 676 tmpl.stream, gettext_functions=keywords): 677 yield lineno, func, message, comments -
genshi/template/base.py
560 560 template files. 561 561 """ 562 562 from genshi.template.loader import TemplateNotFound 563 from genshi.filters.i18n import Translator 563 564 564 565 for event in stream: 565 566 if event[0] is INCLUDE: … … 574 575 try: 575 576 tmpl = self.loader.load(href, relative_to=event[2][0], 576 577 cls=cls or self.__class__) 578 if isinstance(tmpl.filters[0], Translator): 579 # Update xi:include template Translator.i18n_domains 580 # with the ones found on the template which calls 581 # this include 582 tmpl.filters[0].i18n_domains = \ 583 self.filters[0].i18n_domains 577 584 for event in tmpl.generate(ctxt, **vars): 578 585 yield event 579 586 except TemplateNotFound:
