a `#~@s2dZddlZddlZddlmZddlmZddlmZmZddl m Z ddl m Z ddl m Z dd lmZdd lmZmZdd lmZdd lmZdd lmZmZmZddlmZmZmZm Z m!Z!m"Z"gdZ#e$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-2021 by the Babel Team. :license: BSD, see LICENSE for more details. N) parse_header) OrderedDict)datetimetime)get_close_matches)message_from_string)copy) __version__)LocaleUnknownLocaleError)format_datetime) get_plural)distinctLOCALTZFixedOffsetTimezone) string_types number_typesPY2cmp text_type force_text)MessageCatalogTranslationErrorz \% (?:\(([\w]*)\))? ( [-#0\ +]?(?:\*|[\d]+)? (?:\.(?:\*|[\d]+))? [hlL]? ) ([diouxXeEfFgGcrs%]) cCstd|}t|dd}t|}t|}|d}|dur|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) rematchrstrptimegroupmktimerZ fromtimestampintrreplace)valuer"tttsZdtrZ plus_minus_srestZhours_offset_sZ mins_offset_sZ plus_minusZ hours_offsetZ mins_offsetZnet_mins_offsetr,:/usr/lib/python3.9/site-packages/babel/messages/catalog.py_parse_datetime_header,s"      r.c@seZdZdZd!ddZddZd d Zd d Zd dZddZ ddZ ddZ ddZ ddZ d"ddZeddZeddZedd ZdS)#rz0Representation of a single message in a catalog.r,Nc Cs||_|s|jrd}||_tt||_t||_|rJ|jrJ|j dn |j 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/r/z python-formatN)id pluralizablestringlistr locationssetflags python_formatadddiscard auto_comments user_comments isinstancer previous_idlinenocontext) selfr0r2r4r6r:r;r=r>r?r,r,r-__init__Os        zMessage.__init__cCsdt|j|jt|jfS)Nz<%s %r (flags: %r)>)type__name__r0r3r6r@r,r,r-__repr__tszMessage.__repr__cCsdd}t||||S)z0Compare Messages, taking into account plural idscSs4t|tr$|jr$|jd|jp dfS|j|jp0dfS)Nrr/)r<rr1r0r?)objr,r,r-values_to_comparezsz*Message.__cmp__..values_to_compare)r)r@otherrGr,r,r-__cmp__xszMessage.__cmp__cCs||dkSNrrIr@rHr,r,r-__gt__szMessage.__gt__cCs||dkSrJrKrLr,r,r-__lt__szMessage.__lt__cCs||dkSrJrKrLr,r,r-__ge__szMessage.__ge__cCs||dkSrJrKrLr,r,r-__le__szMessage.__le__cCs||dkSrJrKrLr,r,r-__eq__szMessage.__eq__cCs||dkSrJrKrLr,r,r-__ne__szMessage.__ne__c Cs2ttt|j|j|j|j|j|j|j |j |j f SN) rmaprr0r2r4r6r:r;r=r>r?rDr,r,r-clones z Message.clonec Cs\ddlm}g}|D]B}z|||WqtyT}z||WYd}~qd}~00q|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.checkersrVrappend)r@catalogrVerrorsZcheckerer,r,r-checks "z Message.checkcCs d|jvS)aWhether the translation is fuzzy. >>> Message('foo').fuzzy False >>> msg = Message('foo', 'foo', flags=['fuzzy']) >>> msg.fuzzy True >>> msg :type: `bool`fuzzyr6rDr,r,r-r\s z Message.fuzzycCst|jttfS)zWhether the message is plurizable. >>> Message('foo').pluralizable False >>> Message(('foo', 'bar')).pluralizable True :type: `bool`)r<r0r3tuplerDr,r,r-r1s 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|]}t|VqdSrS) PYTHON_FORMATsearch).0r0r,r,r- z(Message.python_format..)r0r<r3r^any)r@Zidsr,r,r-r7s zMessage.python_format)r/r,r,r,r,r,NN)N)rC __module__ __qualname____doc__rArErIrMrNrOrPrQrRrUr[propertyr\r1r7r,r,r,r-rLs( %   rc@seZdZdZdS)rz_Exception thrown by translation checkers when invalid message translations are encountered.N)rCrerfrgr,r,r,r-rsrz# Translations template for PROJECT. # Copyright (C) YEAR ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , YEAR. #cCsDt|d}i}|D]$\}}|d}|d}|||<q|S)Nutf8)rencodeitemsdecode)Z header_stringheadersZdecoded_headersnamer(r,r,r- _parse_headers   roc @s0eZdZdZddeddddddddddf ddZddZdd Zd d Ze eeZ e eZ d d Z ddZ e e e ddZddZddZe eeddZe ddZe ddZe ddZddZdd Zd!d"Zd#d$Zd%d&Zd'd(Zd)d*Zd9d,d-Zd.d/Zd:d0d1Zd;d2d3Zdrz$Representation of a message catalog.NTcCs||_||_||_t|_|p d|_|p*d|_|p4d|_|p>d|_| pHd|_ | pRd|_ | p\d|_ |durtt t}nt|t r|js|jtd }||_| durd } nt| t r| js| 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-8NrzYEAR-MO-DA HO:MI+ZONE)domainlocale_header_commentr _messagesprojectversioncopyright_holdermsgid_bugs_addresslast_translator language_teamcharsetrnowrr<r r' creation_date revision_dater\obsolete _num_plurals _plural_expr)r@rtrsheader_commentrwrxryrzrrr{r|r}r\r,r,r-rAs2          zCatalog.__init__cCs|durd|_d|_dSt|tr6t||_||_dSt|trxt||_zt||_Wntyrd|_Yn0dStd|dS)NzF`locale` must be a Locale, a locale identifier string, or None; got %r) _locale_identifier_localer<r rrparser TypeErrorr@rtr,r,r- _set_locale/s       zCatalog._set_localecCs|jSrS)rrDr,r,r- _get_localeDszCatalog._get_localecCs|jSrS)rrDr,r,r-_get_locale_identifierGszCatalog._get_locale_identifiercCs|j}ttd}t|jdr.|jd}|d|jd|j d|d|j }|j rf|j j n|j }|r|dd|}|S) Nz%YstrftimerprqYEARrrzTranslations templatez%s translations)rurr~rrhasattrrr'rwrxryrtZ english_namelocale_identifier)r@ZcommentZyearZ locale_namer,r,r-_get_header_commentMs  zCatalog._get_header_commentcCs ||_dSrS)ru)r@r2r,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` )doccCsLg}|dd|j|jff|d|jf|dt|jdddft|jtt ft rx|dt|jdddfn|d|jf|d |j f|j r|d t |j f|j rd |jvr|d |jd t |j fn|d |jf|jdur|d |jf|d|dd|jf|d|ddtf|S)NzProject-Id-Versionz%s %szReport-Msgid-Bugs-TozPOT-Creation-Datezyyyy-MM-dd HH:mmZen)rtzPO-Revision-DatezLast-TranslatorZLanguageLANGUAGEz Language-Teamz Plural-Forms)z MIME-Versionz1.0z Content-Typeztext/plain; charset=%s)zContent-Transfer-EncodingZ8bitz Generated-Byz Babel %s )rWrwrxrzr rr<rrtime_rr{rstrr|r'rt plural_formsr}rq)r@rmr,r,r-_get_mime_headerssH   zCatalog._get_mime_headerscCsN|D]B\}}t||jd}t||jd}|dkr`|d}d|dd|_|d|_q|dkrp||_q|dkr||_q|dkr| dd }| |q|d kr||_ q|d krt |\}}d |vr|d |_q|d krt d|\}}t |dd|_|dd|_q|dkr.t||_q|dkrd|vrt||_qdS)N)encodingzproject-id-version zreport-msgid-bugs-tozlast-translatorlanguage-_z language-teamz content-typer}z plural-formsz ;Znpluralsrplural(n != 1)zpot-creation-datezpo-revision-dater)rlowerr}splitjoinrwrxrzr{r'rr|rr&getrrr.rr)r@rmrnr(partsZmimetypeparamsrr,r,r-_set_mime_headerss:        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.|jdur(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)rrtr )r@Znumr,r,r- num_pluralss zCatalog.num_pluralscCs.|jdur(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)' >>> Catalog(locale='ding').plural_expr # unknown locale '(n != 1)' :type: `string_types`Nrr)rrtr )r@exprr,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)rrrDr,r,r-rs zCatalog.plural_formscCs|||jvS)z?Return whether the catalog has a message with the specified ID._key_forrvr@r0r,r,r- __contains__"szCatalog.__contains__cCs t|jS)zeThe number of messages in the catalog. This does not include the special ``msgid ""`` entry.)lenrvrDr,r,r-__len__&szCatalog.__len__ccsng}|jD]\}}|d||fq t}|jr<|dhO}tdd||dV|jD]}|j|VqXdS)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: %sr\r/ r]N) mime_headersrWr5r\rrrv)r@bufrnr(r6keyr,r,r-__iter__,s  zCatalog.__iter__cCs*d}|jrd|j}dt|j|j|fS)Nr/z %sz <%s %r%s>)rtrBrCrsrr,r,r-rE;s zCatalog.__repr__cCs||dS)z)Delete the message with the specified ID.N)deleterr,r,r- __delitem__AszCatalog.__delitem__cCs ||S)zUReturn the message with the specified ID. :param id: the message ID )rrr,r,r- __getitem__EszCatalog.__getitem__cCs t|tsJd|||j}|j|}|r|jrL|jsL|j|_|j|_t t |j |j |_ t t |j |j |_ t t |j |j |_ |j|jO_|}nx|dkrt|j|_ddd|j D|_|j|_n>t|t tfrt|jt tfsJdt|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|qS)z# %s)rstrip)racr,r,r- urcz'Catalog.__setitem__..zExpected sequence but got %sN)r<rrr?rvrr1r0r2r3rr4r:r;r6rorkrrrr\r^rB)r@r0messagercurrentr,r,r- __setitem__Ls:         zCatalog.__setitem__r,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 )r>r?)rr3) r@r0r2r4r6r:r;r=r>r?rr,r,r-r8~s z Catalog.addccs.|jD]}|j|d}|r ||fVq dS)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`` )rXN)rvvaluesr[)r@rrYr,r,r-r[s  z Catalog.checkcCs|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 )rvrr)r@r0r?r,r,r-rsz Catalog.getcCs"|||}||jvr|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 Nrr@r0r?rr,r,r-rs  zCatalog.deleteFcs6jt_g}|s6tfddD}tfdd}|D]}|jrT|j|j}|vr||||qT|st|t r|d} n|} t | | d} | r| d} || } | dur| | f} ||| |qT||j<qTD] } |s | vr| j| <q|r*|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|jr||jfqSr,)r2rr?)ramsgid)messagesr@r,r-rsz"Catalog.update..csH|}d}||krRd}||}t|jtrD|jg|_q^t|j|_n |d}|j |_ rztt |j |_ t|jtt frt|j tt fsd}t |j gdgt |jd|_ n.t |j jkrd}t |j dt |j |_ n"t|j tt frd}|j d|_ |j|jO_|r:|jdhO_||j<dS)NFTr/rrr\)rUr8rr<r0rr=r3popr2rr;r^rrr6)rZoldkeynewkeyr\ZoldmsgZ fuzzy_matcheskeep_user_commentsrZ remainingr@r,r-_merges:      zCatalog.update.._mergerrN)rvrrdictr5r0rr?r<r^rrstripkeysrrr)r@templateZno_fuzzy_matchingZupdate_header_commentrZfuzzy_candidatesrrrZmatchkeymatchesrZnewctxtrr,rr-updatesH5#     zCatalog.updatecCs.|}t|ttfr|d}|dur*||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)r<r3r^rr,r,r-rIs zCatalog._key_for)Nr,r,r,r,r,NN)N)N)FFT)N)"rCrerfrgDEFAULT_HEADERrArrrrhrtrrrrrrrrrrrrrrErrrr8r[rrrrr,r,r,r-rsP : !  3   2 !   r)/rgr!rZcgir collectionsrrrZdifflibrZemailrrZbabelr rqZ babel.corer r Z babel.datesr Zbabel.messages.pluralsr Z babel.utilrrrZ babel._compatrrrrrr__all__compileVERBOSEr_r.objectr Exceptionrrrorr,r,r,r-s6