Edgewall Software

Ticket #129: all_patches_bundle.patch

File all_patches_bundle.patch, 18.4 KB (added by palgarvio, 6 years ago)
  • genshi/filters/i18n.py

     
    1717""" 
    1818 
    1919from compiler import ast 
    20 from gettext import gettext 
     20from gettext import NullTranslations 
    2121import re 
    2222 
    2323from genshi.core import Attrs, Namespace, QName, START, END, TEXT, START_NS, \ 
    2424                        END_NS, XML_NAMESPACE, _ensure 
    25 from genshi.template.base import Template, EXPR, SUB 
     25from genshi.template.base import Template, EXPR, SUB, INCLUDE 
    2626from genshi.template.markup import MarkupTemplate, EXEC 
    2727 
    2828__all__ = ['Translator', 'extract'] 
     
    3535    """Can extract and translate localizable strings from markup streams and 
    3636    templates. 
    3737     
    38     For example, assume the followng template: 
     38    For example, assume the following template: 
    3939     
    4040    >>> from genshi.template import MarkupTemplate 
    4141    >>>  
     
    9191    INCLUDE_ATTRS = frozenset(['abbr', 'alt', 'label', 'prompt', 'standby', 
    9292                               'summary', 'title']) 
    9393 
    94     def __init__(self, translate=gettext, ignore_tags=IGNORE_TAGS, 
     94    def __init__(self, translator=NullTranslations(), ignore_tags=IGNORE_TAGS, 
    9595                 include_attrs=INCLUDE_ATTRS, extract_text=True): 
    9696        """Initialize the translator. 
    9797         
     
    103103                             extracted, or only text in explicit ``gettext`` 
    104104                             function calls 
    105105        """ 
    106         self.translate = translate 
     106        self.translator = translator 
    107107        self.ignore_tags = ignore_tags 
    108108        self.include_attrs = include_attrs 
    109109        self.extract_text = extract_text 
     110        self.i18n_domains = [] 
    110111 
    111112    def __call__(self, stream, ctxt=None, search_text=True, msgbuf=None): 
    112113        """Translate any localizable strings in the given stream. 
     
    126127        """ 
    127128        ignore_tags = self.ignore_tags 
    128129        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 
    130140        if not self.extract_text: 
    131141            search_text = False 
    132142        skip = 0 
    133143        i18n_msg = I18N_NAMESPACE['msg'] 
     144        i18n_choose = I18N_NAMESPACE['choose'] 
     145        i18n_domain = I18N_NAMESPACE['domain'] 
    134146        ns_prefixes = [] 
    135147        xml_lang = XML_NAMESPACE['lang'] 
    136148 
     
    148160            # handle different events that can be localized 
    149161            if kind is START: 
    150162                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                     
    151170                if tag in self.ignore_tags or \ 
    152171                        isinstance(attrs.get(xml_lang), basestring): 
    153172                    skip += 1 
    154173                    yield kind, data, pos 
    155174                    continue 
    156  
     175                     
    157176                new_attrs = [] 
    158177                changed = False 
    159178                for name, value in attrs: 
    160179                    newval = value 
    161180                    if search_text and isinstance(value, basestring): 
    162181                        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) 
    164187                    else: 
    165188                        newval = list(self(_ensure(value), ctxt, 
    166                             search_text=False) 
    167                         ) 
     189                                           search_text=False)) 
    168190                    if newval != value: 
    169191                        value = newval 
    170192                        changed = True 
     
    177199                    continue 
    178200                elif i18n_msg in attrs: 
    179201                    params = attrs.get(i18n_msg) 
    180                     if params and type(params) is list: # event tuple 
    181                         params = params[0][1] 
    182202                    msgbuf = MessageBuffer(params) 
    183203                    attrs -= i18n_msg 
     204                elif i18n_choose in attrs: 
     205                    params = attrs.get(i18n_choose) 
     206                    msgbuf = MessageBuffer(params) 
     207                    attrs -= i18n_choose 
    184208 
    185209                yield kind, (tag, attrs), pos 
    186210 
     
    188212                if not msgbuf: 
    189213                    text = data.strip() 
    190214                    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))) 
    192221                    yield kind, data, pos 
    193222                else: 
    194223                    msgbuf.append(kind, data, pos) 
     
    199228            elif not skip and msgbuf and kind is END: 
    200229                msgbuf.append(kind, data, pos) 
    201230                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): 
    203247                        yield event 
    204248                    msgbuf = None 
    205249                    yield kind, data, pos 
    206  
     250                     
    207251            elif kind is SUB: 
    208252                subkind, substream = data 
    209253                new_substream = list(self(substream, ctxt, msgbuf=msgbuf)) 
     
    214258 
    215259            elif kind is END_NS and data in ns_prefixes: 
    216260                ns_prefixes.remove(data) 
    217  
    218261            else: 
    219262                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() 
    220267 
    221268    GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext', 'dgettext', 'dngettext', 
    222269                         'ugettext', 'ungettext') 
     
    270317            search_text = False 
    271318        skip = 0 
    272319        i18n_msg = I18N_NAMESPACE['msg'] 
     320        i18n_comment = I18N_NAMESPACE['comment'] 
     321        i18n_choose = I18N_NAMESPACE['choose'] 
    273322        xml_lang = XML_NAMESPACE['lang'] 
    274323 
    275324        for kind, data, pos in stream: 
     
    293342                        if name in self.include_attrs: 
    294343                            text = value.strip() 
    295344                            if text: 
    296                                 yield pos[1], None, text 
     345                                yield pos[1], None, text, [] 
    297346                    else: 
    298                         for lineno, funcname, text in self.extract( 
     347                        for lineno, funcname, text, comments in self.extract( 
    299348                                _ensure(value), gettext_functions, 
    300349                                search_text=False): 
    301                             yield lineno, funcname, text 
     350                            yield lineno, funcname, text, comments 
    302351 
    303352                if msgbuf: 
    304353                    msgbuf.append(kind, data, pos) 
    305354                elif i18n_msg in attrs: 
    306355                    params = attrs.get(i18n_msg) 
    307                     if params and type(params) is list: # event tuple 
    308                         params = params[0][1] 
    309356                    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)) 
    310362 
    311363            elif not skip and search_text and kind is TEXT: 
    312364                if not msgbuf: 
    313365                    text = data.strip() 
    314366                    if text and filter(None, [ch.isalpha() for ch in text]): 
    315                         yield pos[1], None, text 
     367                        yield pos[1], None, text, [] 
    316368                else: 
    317369                    msgbuf.append(kind, data, pos) 
    318370 
    319371            elif not skip and msgbuf and kind is END: 
    320372                msgbuf.append(kind, data, pos) 
    321373                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 
    323381                    msgbuf = None 
    324382 
    325383            elif kind is EXPR or kind is EXEC: 
     
    327385                    msgbuf.append(kind, data, pos) 
    328386                for funcname, strings in extract_from_code(data, 
    329387                                                           gettext_functions): 
    330                     yield pos[1], funcname, strings 
     388                    yield pos[1], funcname, strings, [] 
    331389 
    332390            elif kind is SUB: 
    333391                subkind, substream = data 
    334392                messages = self.extract(substream, gettext_functions, 
    335393                                        search_text=search_text and not skip, 
    336394                                        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 
    340397 
     398     
    341399class MessageBuffer(object): 
    342400    """Helper class for managing internationalized mixed content. 
    343401     
     
    352410        :param lineno: the line number on which the first stream event 
    353411                       belonging to the message was found 
    354412        """ 
    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[:] 
    356428        self.lineno = lineno 
    357429        self.string = [] 
     430        self.singular = [] 
     431        self.plural = [] 
    358432        self.events = {} 
    359433        self.values = {} 
    360434        self.depth = 1 
    361435        self.order = 1 
     436        self.choose_order = 1 
    362437        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 
    363443 
    364444    def append(self, kind, data, pos): 
    365445        """Append a stream event to the buffer. 
     
    369449        :param pos: the position of the event in the source 
    370450        """ 
    371451        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) 
    373458            self.events.setdefault(self.stack[-1], []).append(None) 
    374459        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) 
    378469            self.values[param] = (kind, data, pos) 
     470            self.events.setdefault(self.stack[-1], []).append(None) 
    379471        else: 
    380472            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 
    384496                self.depth += 1 
    385                 self.order += 1 
    386497            elif kind is END: 
    387498                self.depth -= 1 
    388499                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() 
    392516 
    393517    def format(self): 
    394518        """Return a message identifier representing the content in the 
    395519        buffer. 
    396520        """ 
     521        if self.singular or self.plural: 
     522            return (u''.join(self.singular).strip(), 
     523                    u''.join(self.plural).strip(), self.choose_numeral) 
    397524        return u''.join(self.string).strip() 
    398525 
    399526    def translate(self, string, regex=re.compile(r'%\((\w+)\)s')): 
     
    545672    tmpl = template_class(fileobj, filename=getattr(fileobj, 'name', None), 
    546673                          encoding=encoding) 
    547674    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

     
    560560        template files. 
    561561        """ 
    562562        from genshi.template.loader import TemplateNotFound 
     563        from genshi.filters.i18n import Translator 
    563564 
    564565        for event in stream: 
    565566            if event[0] is INCLUDE: 
     
    574575                try: 
    575576                    tmpl = self.loader.load(href, relative_to=event[2][0], 
    576577                                            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 
    577584                    for event in tmpl.generate(ctxt, **vars): 
    578585                        yield event 
    579586                except TemplateNotFound: