Navigation

Source code for gettext

"""Internationalization and localization support.This module provides internationalization (I18N) and localization (L10N)support for your Python programs by providing an interface to the GNU gettextmessage catalog library.I18N refers to the operation by which a program is made aware of multiplelanguages. L10N refers to the adaptation of your program, onceinternationalized, to the local language and cultural habits."""# This module represents the integration of work, contributions, feedback, and# suggestions from the following people:## Martin von Loewis, who wrote the initial implementation of the underlying# C-based libintlmodule (later renamed _gettext), along with a skeletal# gettext.py implementation.## Peter Funk, who wrote fintl.py, a fairly complete wrapper around intlmodule,# which also included a pure-Python implementation to read .mo files if# intlmodule wasn't available.## James Henstridge, who also wrote a gettext.py module, which has some# interesting, but currently unsupported experimental features: the notion of# a Catalog class and instances, and the ability to add to a catalog file via# a Python API.## Barry Warsaw integrated these modules, wrote the .install() API and code,# and conformed all C and Python code to Python's coding standards.## Francois Pinard and Marc-Andre Lemburg also contributed valuably to this# module.## J. David Ibanez implemented plural forms. Bruno Haible fixed some bugs.## TODO:# - Lazy loading of .mo files. Currently the entire catalog is loaded into# memory, but that's probably bad for large translated programs. Instead,# the lexical sort of original strings in GNU .mo files should be exploited# to do binary searches and lazy initializations. Or you might want to use# the undocumented double-hash algorithm for .mo files with hash tables, but# you'll need to study the GNU gettext code to do this.## - Support Solaris .mo file formats. Unfortunately, we've been unable to# find this format documented anywhere.importlocale,copy,io,os,re,struct,sysfromerrnoimportENOENT__all__=['NullTranslations','GNUTranslations','Catalog','find','translation','install','textdomain','bindtextdomain','bind_textdomain_codeset','dgettext','dngettext','gettext','lgettext','ldgettext','ldngettext','lngettext','ngettext',]_default_localedir=os.path.join(sys.base_prefix,'share','locale')# Expression parsing for plural form selection.## The gettext library supports a small subset of C syntax. The only# incompatible difference is that integer literals starting with zero are# decimal.## https://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms# http://git.savannah.gnu.org/cgit/gettext.git/tree/gettext-runtime/intl/plural.y_token_pattern=re.compile(r""" (?P<WHITESPACES>[ \t]+) | # spaces and horizontal tabs (?P<NUMBER>[0-9]+\b) | # decimal integer (?P<NAME>n\b) | # only n is allowed (?P<PARENTHESIS>[()]) | (?P<OPERATOR>[-*/%+?:]|[><!]=?|==|&&|\|\|) | # !, *, /, %, +, -, <, >, # <=, >=, ==, !=, &&, ||, # ? : # unary and bitwise ops # not allowed (?P<INVALID>\w+|.) # invalid token """,re.VERBOSE|re.DOTALL)def_tokenize(plural):formoinre.finditer(_token_pattern,plural):kind=mo.lastgroupifkind=='WHITESPACES':continuevalue=mo.group(kind)ifkind=='INVALID':raiseValueError('invalid token in plural form: %s'%value)yieldvalueyield''def_error(value):ifvalue:returnValueError('unexpected token in plural form: %s'%value)else:returnValueError('unexpected end of plural form')_binary_ops=(('||',),('&&',),('==','!='),('<','>','<=','>='),('+','-'),('*','/','%'),)_binary_ops={op:ifori,opsinenumerate(_binary_ops,1)foropinops}_c2py_ops={'||':'or','&&':'and','/':'//'}def_parse(tokens,priority=-1):result=''nexttok=next(tokens)whilenexttok=='!':result+='not 'nexttok=next(tokens)ifnexttok=='(':sub,nexttok=_parse(tokens)result='%s(%s)'%(result,sub)ifnexttok!=')':raiseValueError('unbalanced parenthesis in plural form')elifnexttok=='n':result='%s%s'%(result,nexttok)else:try:value=int(nexttok,10)exceptValueError:raise_error(nexttok)fromNoneresult='%s%d'%(result,value)nexttok=next(tokens)j=100whilenexttokin_binary_ops:i=_binary_ops[nexttok]ifi<priority:break# Break chained comparisonsifiin(3,4)andjin(3,4):# '==', '!=', '<', '>', '<=', '>='result='(%s)'%result# Replace some C operators by their Python equivalentsop=_c2py_ops.get(nexttok,nexttok)right,nexttok=_parse(tokens,i+1)result='%s%s%s'%(result,op,right)j=iifj==priority==4:# '<', '>', '<=', '>='result='(%s)'%resultifnexttok=='?'andpriority<=0:if_true,nexttok=_parse(tokens,0)ifnexttok!=':':raise_error(nexttok)if_false,nexttok=_parse(tokens)result='%s if %s else %s'%(if_true,result,if_false)ifpriority==0:result='(%s)'%resultreturnresult,nexttokdef_as_int(n):try:i=round(n)exceptTypeError:raiseTypeError('Plural value must be an integer, got %s'%(n.__class__.__name__,))fromNonereturnndefc2py(plural):"""Gets a C expression as used in PO files for plural forms and returns a Python function that implements an equivalent expression. """iflen(plural)>1000:raiseValueError('plural form expression is too long')try:result,nexttok=_parse(_tokenize(plural))ifnexttok:raise_error(nexttok)depth=0forcinresult:ifc=='(':depth+=1ifdepth>20:# Python compiler limit is about 90.# The most complex example has 2.raiseValueError('plural form expression is too complex')elifc==')':depth-=1ns={'_as_int':_as_int}exec('''if True: def func(n): if not isinstance(n, int): n = _as_int(n) return int(%s) '''%result,ns)returnns['func']exceptRecursionError:# Recursion error can be raised in _parse() or exec().raiseValueError('plural form expression is too complex')def_expand_lang(loc):loc=locale.normalize(loc)COMPONENT_CODESET=1<<0COMPONENT_TERRITORY=1<<1COMPONENT_MODIFIER=1<<2# split up the locale into its base componentsmask=0pos=loc.find('@')ifpos>=0:modifier=loc[pos:]loc=loc[:pos]mask|=COMPONENT_MODIFIERelse:modifier=''pos=loc.find('.')ifpos>=0:codeset=loc[pos:]loc=loc[:pos]mask|=COMPONENT_CODESETelse:codeset=''pos=loc.find('_')ifpos>=0:territory=loc[pos:]loc=loc[:pos]mask|=COMPONENT_TERRITORYelse:territory=''language=locret=[]foriinrange(mask+1):ifnot(i&~mask):# if all components for this combo exist ...val=languageifi&COMPONENT_TERRITORY:val+=territoryifi&COMPONENT_CODESET:val+=codesetifi&COMPONENT_MODIFIER:val+=modifierret.append(val)ret.reverse()returnret

classGNUTranslations(NullTranslations):# Magic number of .mo filesLE_MAGIC=0x950412deBE_MAGIC=0xde120495# Acceptable .mo versionsVERSIONS=(0,1)def_get_versions(self,version):"""Returns a tuple of major version, minor version"""return(version>>16,version&0xffff)def_parse(self,fp):"""Override this method to support alternative .mo formats."""unpack=struct.unpackfilename=getattr(fp,'name','')# Parse the .mo file header, which consists of 5 little endian 32# bit words.self._catalog=catalog={}self.plural=lambdan:int(n!=1)# germanic plural by defaultbuf=fp.read()buflen=len(buf)# Are we big endian or little endian?magic=unpack('<I',buf[:4])[0]ifmagic==self.LE_MAGIC:version,msgcount,masteridx,transidx=unpack('<4I',buf[4:20])ii='<II'elifmagic==self.BE_MAGIC:version,msgcount,masteridx,transidx=unpack('>4I',buf[4:20])ii='>II'else:raiseOSError(0,'Bad magic number',filename)major_version,minor_version=self._get_versions(version)ifmajor_versionnotinself.VERSIONS:raiseOSError(0,'Bad version number '+str(major_version),filename)# Now put all messages from the .mo file buffer into the catalog# dictionary.foriinrange(0,msgcount):mlen,moff=unpack(ii,buf[masteridx:masteridx+8])mend=moff+mlentlen,toff=unpack(ii,buf[transidx:transidx+8])tend=toff+tlenifmend<buflenandtend<buflen:msg=buf[moff:mend]tmsg=buf[toff:tend]else:raiseOSError(0,'File is corrupt',filename)# See if we're looking at GNU .mo conventions for metadataifmlen==0:# Catalog descriptionlastk=Noneforb_itemintmsg.split('\n'.encode("ascii")):item=b_item.decode().strip()ifnotitem:continuek=v=Noneif':'initem:k,v=item.split(':',1)k=k.strip().lower()v=v.strip()self._info[k]=vlastk=keliflastk:self._info[lastk]+='\n'+itemifk=='content-type':self._charset=v.split('charset=')[1]elifk=='plural-forms':v=v.split(';')plural=v[1].split('plural=')[1]self.plural=c2py(plural)# Note: we unconditionally convert both msgids and msgstrs to# Unicode using the character encoding specified in the charset# parameter of the Content-Type header. The gettext documentation# strongly encourages msgids to be us-ascii, but some applications# require alternative encodings (e.g. Zope's ZCML and ZPT). For# traditional gettext applications, the msgid conversion will# cause no problems since us-ascii should always be a subset of# the charset encoding. We may want to fall back to 8-bit msgids# if the Unicode conversion fails.charset=self._charsetor'ascii'ifb'\x00'inmsg:# Plural formsmsgid1,msgid2=msg.split(b'\x00')tmsg=tmsg.split(b'\x00')msgid1=str(msgid1,charset)fori,xinenumerate(tmsg):catalog[(msgid1,i)]=str(x,charset)else:catalog[str(msg,charset)]=str(tmsg,charset)# advance to next entry in the seek tablesmasteridx+=8transidx+=8

[docs]deffind(domain,localedir=None,languages=None,all=False):# Get some reasonable defaults for arguments that were not suppliediflocaledirisNone:localedir=_default_localediriflanguagesisNone:languages=[]forenvarin('LANGUAGE','LC_ALL','LC_MESSAGES','LANG'):val=os.environ.get(envar)ifval:languages=val.split(':')breakif'C'notinlanguages:languages.append('C')# now normalize and expand the languagesnelangs=[]forlanginlanguages:fornelangin_expand_lang(lang):ifnelangnotinnelangs:nelangs.append(nelang)# select a languageifall:result=[]else:result=Noneforlanginnelangs:iflang=='C':breakmofile=os.path.join(localedir,lang,'LC_MESSAGES','%s.mo'%domain)ifos.path.exists(mofile):ifall:result.append(mofile)else:returnmofilereturnresult