3 eY y@s dZddlZddlZddlmZddlmZmZddlmZddl m Z ddl m Z ddl m Zdd lmZdd lmZdd lmZdd lmZmZmZmZdd lmZmZmZmZdddgZej dej!Z"ddZ#Gddde$Z%Gddde&Z'dZ(erddZ)ne Z)Gddde$Z*dS)z babel.messages.catalog ~~~~~~~~~~~~~~~~~~~~~~ Data structures for message catalogs. :copyright: (c) 2013 by the Babel Team. :license: BSD, see LICENSE for more details. N) parse_header)datetimetime)get_close_matches)message_from_string)copy) __version__)Locale)format_datetime) get_plural)odictdistinctLOCALTZFixedOffsetTimezone) string_types number_typesPY2cmpMessageCatalogTranslationErrorz \% (?:\(([\w]*)\))? ( [-#0\ +]?(?:\*|[\d]+)? (?:\.(?:\*|[\d]+))? [hlL]? ) ([diouxXeEfFgGcrs%]) cCstjd|}tj|jdd}tj|}tj|}|jd}|dk r|d|dd}}|dd|dd}} t|d} t|} t| } | d } | | 7} | | 9} t | }|j |d }|S) Nz+^(?P.*?)(?P[+-]\d{4})?$rz%Y-%m-%d %H:%Mtzoffsetr1<)tzinfo) rematchrZstrptimegroupZmktimerZ fromtimestampintrreplace)valuerZttZtsZdtrZ plus_minus_srestZhours_offset_sZ mins_offset_sZ plus_minusZ hours_offsetZ mins_offsetZnet_mins_offsetr$/usr/lib/python3.6/catalog.py_parse_datetime_header+s"      r&c@seZdZdZdfffffddfddZddZdd Zd d Zd d ZddZ ddZ ddZ ddZ ddZ d ddZeddZeddZeddZdS)!rz0Representation of a single message in a catalog.Nc Cs||_| r|jrd}||_tt||_t||_|rL|jrL|jj dn |jj dtt||_ tt||_ t |tr|g|_n t||_||_| |_dS)a_Create the message object. :param id: the message ID, or a ``(singular, plural)`` tuple for pluralizable messages :param string: the translated message string, or a ``(singular, plural)`` tuple for pluralizable messages :param locations: a sequence of ``(filename, lineno)`` tuples :param flags: a set or sequence of flags :param auto_comments: a sequence of automatic comments for the message :param user_comments: a sequence of user comments for the message :param previous_id: the previous message ID, or a ``(singular, plural)`` tuple for pluralizable messages :param lineno: the line number on which the msgid line was found in the PO file, if any :param context: the message context r'z python-formatN)r'r')id pluralizablestringlistr locationssetflags python_formatadddiscard auto_comments user_comments isinstancer previous_idlinenocontext) selfr(r*r,r.r2r3r5r6r7r$r$r%__init__Ns        zMessage.__init__cCsdt|j|jt|jfS)Nz<%s %r (flags: %r)>)type__name__r(r+r.)r8r$r$r%__repr__sszMessage.__repr__cCsdd}t||||S)z0Compare Messages, taking into account plural idscSs4t|tr$|jr$|jd|jp dfS|j|jp0dfS)Nrr')r4rr)r(r7)objr$r$r%values_to_compareysz*Message.__cmp__..values_to_compare)r)r8otherr>r$r$r%__cmp__wszMessage.__cmp__cCs|j|dkS)Nr)r@)r8r?r$r$r%__gt__szMessage.__gt__cCs|j|dkS)Nr)r@)r8r?r$r$r%__lt__szMessage.__lt__cCs|j|dkS)Nr)r@)r8r?r$r$r%__ge__szMessage.__ge__cCs|j|dkS)Nr)r@)r8r?r$r$r%__le__szMessage.__le__cCs|j|dkS)Nr)r@)r8r?r$r$r%__eq__szMessage.__eq__cCs|j|dkS)Nr)r@)r8r?r$r$r%__ne__szMessage.__ne__c Cs2ttt|j|j|j|j|j|j|j |j |j f S)N) rmaprr(r*r,r.r2r3r5r6r7)r8r$r$r%clonesz Message.clonecCs^ddlm}g}xH|D]@}y|||Wqtk rT}z|j|WYdd}~XqXqW|S)aRun various validation checks on the message. Some validations are only performed if the catalog is provided. This method returns a sequence of `TranslationError` objects. :rtype: ``iterator`` :param catalog: A catalog instance that is passed to the checkers :see: `Catalog.check` for a way to perform checks for all messages in a catalog. r)checkersN)Zbabel.messages.checkersrIrappend)r8catalogrIerrorsZcheckerer$r$r%checks   z Message.checkcCs d|jkS)aWhether the translation is fuzzy. >>> Message('foo').fuzzy False >>> msg = Message('foo', 'foo', flags=['fuzzy']) >>> msg.fuzzy True >>> msg :type: `bool`fuzzy)r.)r8r$r$r%rOs z Message.fuzzycCst|jttfS)zWhether the message is plurizable. >>> Message('foo').pluralizable False >>> Message(('foo', 'bar')).pluralizable True :type: `bool`)r4r(r+tuple)r8r$r$r%r)s zMessage.pluralizablecCs,|j}t|ttfs|g}tdd|DS)zWhether the message contains Python-style parameters. >>> Message('foo %(name)s bar').python_format True >>> Message(('foo %(name)s', 'foo %(name)s')).python_format True :type: `bool`css|]}tj|VqdS)N) PYTHON_FORMATsearch).0r(r$r$r% sz(Message.python_format..)r(r4r+rPany)r8Zidsr$r$r%r/s zMessage.python_format)N)r; __module__ __qualname____doc__r9r<r@rArBrCrDrErFrHrNpropertyrOr)r/r$r$r$r%rKs $   c@seZdZdZdS)rz_Exception thrown by translation checkers when invalid message translations are encountered.N)r;rVrWrXr$r$r$r%rsz# Translations template for PROJECT. # Copyright (C) YEAR ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , YEAR. #cCsHt|jd}i}x0|jD]$\}}|jd}|jd}|||<qW|S)Nutf8)rencodeitemsdecode)Z header_stringheadersZdecoded_headersnamer"r$r$r% _parse_headers   r`c @seZdZdZddeddddddddddf ddZddZdd Zeeed d Z d d Z ddZ ee e dd Z eddZ eddZeddZddZddZddZddZdd Zd!d"Zd#d$Zdfffffddfd%d&Zd'd(Zd2d)d*Zd3d+d,Zd4d.d/Zd5d0d1ZdS)6rz$Representation of a message catalog.NTcCs||_|rtj|}||_||_t|_|p.d|_|p8d|_|pBd|_ |pLd|_ | pVd|_ | p`d|_ | pjd|_ |dkrtjt}nt|tr|j r|jtd }||_| dkrd } nt| tr| j r| jtd } | |_| |_t|_d|_d|_dS) aDInitialize the catalog object. :param locale: the locale identifier or `Locale` object, or `None` if the catalog is not bound to a locale (which basically means it's a template) :param domain: the message domain :param header_comment: the header comment as string, or `None` for the default header :param project: the project's name :param version: the project's version :param copyright_holder: the copyright holder of the catalog :param msgid_bugs_address: the email address or URL to submit bug reports to :param creation_date: the date the catalog was created :param revision_date: the date the catalog was revised :param last_translator: the name and email of the last translator :param language_team: the name and email of the language team :param charset: the encoding to use in the output (defaults to utf-8) :param fuzzy: the fuzzy bit on the catalog header PROJECTVERSION ORGANIZATIONz EMAIL@ADDRESSzFULL NAME zLANGUAGE zutf-8N)rzYEAR-MO-DA HO:MI+ZONE)domainr parselocale_header_commentr _messagesprojectversioncopyright_holdermsgid_bugs_addresslast_translator language_teamcharsetrnowrr4rr! creation_date revision_daterOobsolete _num_plurals _plural_expr)r8rfrdheader_commentrirjrkrlrqrrrmrnrorOr$r$r%r9s6           zCatalog.__init__cCsv|j}tjtjd}t|jdr.|jjd}|jd|jjd|j jd|jd|j }|j rr|jdd|j j }|S) Nz%YstrftimerarbYEARrczTranslations templatez%s translations) rgrrprrwhasattrrrr!rirjrkrfZ english_name)r8ZcommentZyearr$r$r%_get_header_comment0s     zCatalog._get_header_commentcCs ||_dS)N)rg)r8r*r$r$r%_set_header_comment>szCatalog._set_header_commenta The header comment for the catalog. >>> catalog = Catalog(project='Foobar', version='1.0', ... copyright_holder='Foo Company') >>> print(catalog.header_comment) #doctest: +ELLIPSIS # Translations template for Foobar. # Copyright (C) ... Foo Company # This file is distributed under the same license as the Foobar project. # FIRST AUTHOR , .... # The header can also be set from a string. Any known upper-case variables will be replaced when the header is retrieved again: >>> catalog = Catalog(project='Foobar', version='1.0', ... copyright_holder='Foo Company') >>> catalog.header_comment = '''\ ... # The POT for my really cool PROJECT project. ... # Copyright (C) 1990-2003 ORGANIZATION ... # This file is distributed under the same license as the PROJECT ... # project. ... #''' >>> print(catalog.header_comment) # The POT for my really cool Foobar project. # Copyright (C) 1990-2003 Foo Company # This file is distributed under the same license as the Foobar # project. # :type: `unicode` )doccCsTg}|jdd|j|jff|jd|jf|jdt|jdddft|jtt ft rx|jdt|jdddfn|jd|jf|jd |j f|j dk r|jd t |j f|j dk rd |jkr|jd |jjd t |j fn|jd |jf|j dk r|jd |jf|jd|jdd|jf|jd|jddtf|S)NzProject-Id-Versionz%s %szReport-Msgid-Bugs-TozPOT-Creation-Datezyyyy-MM-dd HH:mmZZen)rfzPO-Revision-DatezLast-TranslatorZLanguageZLANGUAGEz Language-Teamz Plural-Forms MIME-Version1.0z Content-Typeztext/plain; charset=%sContent-Transfer-Encoding8bitz Generated-Byz Babel %s )r}r~)rr)rJrirjrlr rqr4rrrtime_rrmrfstrrnr! plural_formsrorb)r8r^r$r$r%_get_mime_headersbs8     zCatalog._get_mime_headerscCs>x6|D],\}}|j}|dkrL|jd}dj|dd|_|d|_q|dkr\||_q|dkrl||_q|dkr|jdd}tj ||_ q|d kr||_ q|d krt |\}}d |kr|d j|_ q|d krt d |\}}t|jdd|_|jdd|_q|dkrt||_q|dkrd|krt||_qWdS)Nzproject-id-version rzreport-msgid-bugs-tozlast-translatorZlanguage-_z language-teamz content-typeroz plural-formsz ;ZnpluralsrZpluralz(n != 1)zpot-creation-datezpo-revision-daterxr)lowersplitjoinrirjrlrmr!r rerfrnrror getrtrur&rqrr)r8r^r_r"partsZmimetypeZparamsrr$r$r%_set_mime_headerss8       zCatalog._set_mime_headersa The MIME headers of the catalog, used for the special ``msgid ""`` entry. The behavior of this property changes slightly depending on whether a locale is set or not, the latter indicating that the catalog is actually a template for actual translations. Here's an example of the output for such a catalog template: >>> from babel.dates import UTC >>> created = datetime(1990, 4, 1, 15, 30, tzinfo=UTC) >>> catalog = Catalog(project='Foobar', version='1.0', ... creation_date=created) >>> for name, value in catalog.mime_headers: ... print('%s: %s' % (name, value)) Project-Id-Version: Foobar 1.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 1990-04-01 15:30+0000 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel ... And here's an example of the output when the locale is set: >>> revised = datetime(1990, 8, 3, 12, 0, tzinfo=UTC) >>> catalog = Catalog(locale='de_DE', project='Foobar', version='1.0', ... creation_date=created, revision_date=revised, ... last_translator='John Doe ', ... language_team='de_DE ') >>> for name, value in catalog.mime_headers: ... print('%s: %s' % (name, value)) Project-Id-Version: Foobar 1.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 1990-04-01 15:30+0000 PO-Revision-Date: 1990-08-03 12:00+0000 Last-Translator: John Doe Language: de_DE Language-Team: de_DE Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel ... :type: `list` cCs.|jdkr(d}|jr"t|jd}||_|jS)zThe number of plurals used by the catalog or locale. >>> Catalog(locale='en').num_plurals 2 >>> Catalog(locale='ga').num_plurals 5 :type: `int`Nrr)rtrfr )r8Znumr$r$r% num_pluralss zCatalog.num_pluralscCs.|jdkr(d}|jr"t|jd}||_|jS)a The plural expression used by the catalog or locale. >>> Catalog(locale='en').plural_expr '(n != 1)' >>> Catalog(locale='ga').plural_expr '(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4)' :type: `string_types`Nz(n != 1)r)rurfr )r8exprr$r$r% plural_exprs zCatalog.plural_exprcCsd|j|jfS)zReturn the plural forms declaration for the locale. >>> Catalog(locale='en').plural_forms 'nplurals=2; plural=(n != 1)' >>> Catalog(locale='pt_BR').plural_forms 'nplurals=2; plural=(n > 1)' :type: `str`znplurals=%s; plural=%s)rr)r8r$r$r%rs zCatalog.plural_formscCs|j||jkS)z?Return whether the catalog has a message with the specified ID.)_key_forrh)r8r(r$r$r% __contains__szCatalog.__contains__cCs t|jS)zeThe number of messages in the catalog. This does not include the special ``msgid ""`` entry.)lenrh)r8r$r$r%__len__szCatalog.__len__ccszg}x$|jD]\}}|jd||fq Wt}|jrD|tdgO}tddj||dVx|jD]}|j|VqbWdS)zIterates through all the entries in the catalog, in the order they were added, yielding a `Message` object for every entry. :rtype: ``iterator``z%s: %srOr' )r.N) mime_headersrJr-rOrrrh)r8Zbufr_r"r.keyr$r$r%__iter__ s zCatalog.__iter__cCs*d}|jrd|j}dt|j|j|fS)Nr'z %sz <%s %r%s>)rfr:r;rd)r8rfr$r$r%r<s zCatalog.__repr__cCs|j|dS)z)Delete the message with the specified ID.N)delete)r8r(r$r$r% __delitem__!szCatalog.__delitem__cCs |j|S)zUReturn the message with the specified ID. :param id: the message ID )r)r8r(r$r$r% __getitem__%szCatalog.__getitem__cCs"t|tstd|j||j}|jj|}|r|jrN|j rN|j|_|j |_ t t |j |j |_ t t |j |j |_ t t |j|j|_|j|jO_|}nx|dkrt|j j|_djdd|jD|_|j|_n>t|t tfrt|j t tfstdt|j ||j|<dS)aAdd or update the message with the specified ID. >>> catalog = Catalog() >>> catalog[u'foo'] = Message(u'foo') >>> catalog[u'foo'] If a message with that ID is already in the catalog, it is updated to include the locations and flags of the new message. >>> catalog = Catalog() >>> catalog[u'foo'] = Message(u'foo', locations=[('main.py', 1)]) >>> catalog[u'foo'].locations [('main.py', 1)] >>> catalog[u'foo'] = Message(u'foo', locations=[('utils.py', 5)]) >>> catalog[u'foo'].locations [('main.py', 1), ('utils.py', 5)] :param id: the message ID :param message: the `Message` object zexpected a Message objectr'rcSsg|]}d|jqS)z# %s)rstrip)rScr$r$r% Usz'Catalog.__setitem__..zExpected sequence but got %sN)r4rAssertionErrorrr7rhrr)r(r*r+r r,r2r3r.r`r\rrrvrOrPr:)r8r(messagerZcurrentr$r$r% __setitem__,s0   zCatalog.__setitem__c Cs*t||t||||||| d } | ||<| S)atAdd or update the message with the specified ID. >>> catalog = Catalog() >>> catalog.add(u'foo') >>> catalog[u'foo'] This method simply constructs a `Message` object with the given arguments and invokes `__setitem__` with that object. :param id: the message ID, or a ``(singular, plural)`` tuple for pluralizable messages :param string: the translated message string, or a ``(singular, plural)`` tuple for pluralizable messages :param locations: a sequence of ``(filename, lineno)`` tuples :param flags: a set or sequence of flags :param auto_comments: a sequence of automatic comments :param user_comments: a sequence of user comments :param previous_id: the previous message ID, or a ``(singular, plural)`` tuple for pluralizable messages :param lineno: the line number on which the msgid line was found in the PO file, if any :param context: the message context )r6r7)rr+) r8r(r*r,r.r2r3r5r6r7rr$r$r%r0^s z Catalog.addccs2x,|jjD]}|j|d}|r ||fVq WdS)aBRun various validation checks on the translations in the catalog. For every message which fails validation, this method yield a ``(message, errors)`` tuple, where ``message`` is the `Message` object and ``errors`` is a sequence of `TranslationError` objects. :rtype: ``iterator`` )rKN)rhvaluesrN)r8rrLr$r$r%rNs  z Catalog.checkcCs|jj|j||S)zReturn the message with the specified ID and context. :param id: the message ID :param context: the message context, or ``None`` for no context )rhrr)r8r(r7r$r$r%rsz Catalog.getcCs"|j||}||jkr|j|=dS)zDelete the message with the specified ID and context. :param id: the message ID :param context: the message context, or ``None`` for no context N)rrh)r8r(r7rr$r$r%rs  zCatalog.deleteFc sDjjt_g}|s6tfddD}tfdd}x|D]}|jrTj|j|j}|kr||||qT|dkrt|t r|d}n|}t |j j |j d} | r| d} || } | dk r| | f} ||| |qT||j<qTWx,D]$} |s| kr| j| <qW|r8|j_|j_dS) aUpdate the catalog based on the given template catalog. >>> from babel.messages import Catalog >>> template = Catalog() >>> template.add('green', locations=[('main.py', 99)]) >>> template.add('blue', locations=[('main.py', 100)]) >>> template.add(('salad', 'salads'), locations=[('util.py', 42)]) >>> catalog = Catalog(locale='de_DE') >>> catalog.add('blue', u'blau', locations=[('main.py', 98)]) >>> catalog.add('head', u'Kopf', locations=[('util.py', 33)]) >>> catalog.add(('salad', 'salads'), (u'Salat', u'Salate'), ... locations=[('util.py', 38)]) >>> catalog.update(template) >>> len(catalog) 3 >>> msg1 = catalog['green'] >>> msg1.string >>> msg1.locations [('main.py', 99)] >>> msg2 = catalog['blue'] >>> msg2.string u'blau' >>> msg2.locations [('main.py', 100)] >>> msg3 = catalog['salad'] >>> msg3.string (u'Salat', u'Salate') >>> msg3.locations [('util.py', 42)] Messages that are in the catalog but not in the template are removed from the main collection, but can still be accessed via the `obsolete` member: >>> 'head' in catalog False >>> list(catalog.obsolete.values()) [] :param template: the reference catalog, usually read from a POT file :param no_fuzzy_matching: whether to use fuzzy matching of message IDs cs.g|]&}|r|jrj||jfqSr$)r*rr7)rSmsgid)messagesr8r$r%rsz"Catalog.update..cs8|j}d}||krRd}j|j|}t|jtrD|jg|_q^t|j|_n j|d}|j |_ t|jtt frt|j tt fsd}t |j gdgt |jd|_ n.t |j j krd}t |j dt |j |_ n"t|j tt frd}|j d|_ |j |j O_ |r*|j tdgO_ ||j<dS)NFTr'rrrO)rHr0rr4r(rr5r+popr*rPrrr.r-)rZoldkeynewkeyrOZoldmsg) fuzzy_matchesr remainingr8r$r%_merges4     " zCatalog.update.._mergeFrrN)rhrr dictr-r(rr7r4rPrrstripkeysrsrvrq) r8templateZno_fuzzy_matchingZupdate_header_commentZfuzzy_candidatesrrrZmatchkeyZmatchesrZnewctxtrr$)rrrr8r%updatesF5         zCatalog.updatecCs.|}t|ttfr|d}|dk r*||f}|S)zThe key for a message is just the singular ID even for pluralizable messages, but is a ``(msgid, msgctxt)`` tuple for context-specific messages. rN)r4r+rP)r8r(r7rr$r$r%r%s zCatalog._key_for)N)N)FF)N)r;rVrWrXDEFAULT_HEADERr9rzr{rYrvrrrrrrrrrr<rrrr0rNrrrrr$r$r$r%rs@8 1   2   )+rXrrZcgirrrZdifflibrZemailrrZbabelrrbZ babel.corer Z babel.datesr Zbabel.messages.pluralsr Z babel.utilr r rrZ babel._compatrrrr__all__compileVERBOSErQr&objectr Exceptionrrr`rr$r$r$r% s2