Edgewall Software

Ticket #129: all_patches_bundle.patch

File all_patches_bundle.patch, 18.4 KB (added by palgarvio, 7 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: