File indexing completed on 2024-03-24 05:47:37
0001 # -*- coding: UTF-8 -*- 0002 # pology.__init__ 0003 0004 """ 0005 The Pology Python library is a package for custom processing of PO files 0006 in field environments. It provides the foundation for Pology end-user tools. 0007 0008 Core Pology objects -- abstractions of PO catalog and its entries -- are 0009 designed to allow quick writing of robust scripts. By default, the correctness 0010 of processed objects is strictly enforced, but such that the user may easily 0011 switch it off for better performance. Modifications to PO files on disk are 0012 always explicit, and Pology tries to change as few lines as possible to be 0013 friendly to version control systems. 0014 0015 Pology provides utility various modules for typical processing needs of 0016 different kinds of data in PO files. These include word-splitting, 0017 markup handling, wrapping, comment parsing, summary reporting, 0018 validation, etc. 0019 0020 Pology also contains language-specific and project-specific modules, 0021 for functionality that is tightly linked to particular languages 0022 and translation projects. 0023 0024 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0025 @author: Sébastien Renard <sebastien.renard@digitalfox.org> 0026 @author: Nicolas Ternisien <nicolas.ternisien@gmail.com> 0027 @author: Goran Rakic (Горан Ракић) <grakic@devbase.net> 0028 @author: Nick Shaforostoff (Николай Шафоростов) <shaforostoff@kde.ru> 0029 0030 @license: GPLv3 0031 """ 0032 0033 import gettext 0034 import os 0035 import re 0036 0037 0038 from pology.colors import ColorString 0039 0040 0041 def datadir (): 0042 """ 0043 Get data directory of Pology installation. 0044 0045 @return: absolute directory path 0046 @rtype: string 0047 """ 0048 0049 datadir = "@CONFIG_DATADIR@" # configured if installed 0050 if not os.path.isdir(datadir): # if running from source dir 0051 srcdir = os.path.dirname(os.path.dirname(__file__)) 0052 datadir = srcdir 0053 return datadir 0054 0055 0056 def localedir (): 0057 """ 0058 Get locale directory of Pology installation. 0059 0060 @return: absolute directory path 0061 @rtype: string 0062 """ 0063 0064 localedir = "@CONFIG_LOCALEDIR@" # configured if installed 0065 if not os.path.isdir(localedir): # if running from source dir 0066 srcdir = os.path.dirname(os.path.dirname(__file__)) 0067 localedir = os.path.join(srcdir, "mo") 0068 return localedir 0069 0070 0071 def version (): 0072 """ 0073 Get Pology version string. 0074 0075 @return: version string 0076 @rtype: string 0077 """ 0078 0079 verstr = "@CONFIG_VERSION@" # configured if installed 0080 if verstr.startswith("@"): # if running from source dir 0081 try: 0082 verfile = os.path.join(datadir(), "VERSION") 0083 for line in open(verfile, encoding='utf-8'): 0084 line = line.strip() 0085 if line: 0086 verstr = line 0087 break 0088 except: 0089 pass 0090 0091 return verstr 0092 0093 0094 def version_info (): 0095 """ 0096 Get Pology version information. 0097 0098 Pology version information consists of three version numbers 0099 (major, minor, bugfix) and an arbitrary suffix (may be empty). 0100 0101 @return: version tuple (major, minor, bugfix, suffix) 0102 @rtype: (int, int, int, string) 0103 """ 0104 0105 verstr = version() 0106 verrx = re.compile(r"^(\d+)\.(\d+)\.?(\d+)?(.*)$") 0107 m = verrx.match(verstr) 0108 major, minor, bugfix = list(map(int, [x or "0" for x in m.groups()[:3]])) 0109 suffix = m.groups()[-1] 0110 verinfo = (major, minor, bugfix, suffix) 0111 0112 return verinfo 0113 0114 0115 # Collect data paths. 0116 0117 # Setup translations. 0118 try: 0119 _tr = gettext.translation("pology", localedir()) 0120 except IOError: 0121 _tr = gettext.NullTranslations() 0122 0123 0124 def _ (_ctxt_, _text_, **kwargs): 0125 """ 0126 Get translation of the text into user's language. 0127 0128 If there are any formatting directives in the text, 0129 they should be named; 0130 the arguments which substitute them are given 0131 as keyword values following the text. 0132 0133 @param _ctxt_: the context in which the text is used 0134 @type _ctxt_: string 0135 @param _text_: the text to translate 0136 @type _text_: string 0137 @return: translated text if available, otherwise original 0138 @rtype: L{ColorString<colors.ColorString>} 0139 """ 0140 0141 ts = TextTrans() 0142 ts._init(_ctxt_, _text_, None, kwargs) 0143 return ts.to_string() 0144 0145 0146 def n_ (_ctxt_, _stext_, _ptext_, **kwargs): 0147 """ 0148 Get translation of the singular/plural text into user's language. 0149 0150 If there are any formatting directives in the text, 0151 they should be named; 0152 the arguments which substitute them are given 0153 as keyword values following the text. 0154 0155 The plural deciding number is given by the C{num} keyword argument. 0156 If no such key exists, or its value is not an integer, an error is raised. 0157 0158 @param _ctxt_: the context in which the text is used 0159 @type _ctxt_: string 0160 @param _stext_: the text to translate for the singular case 0161 @type _stext_: string 0162 @param _ptext_: the text to translate for the plural case 0163 @type _ptext_: string 0164 @return: translated text if available, otherwise original 0165 @rtype: L{ColorString<colors.ColorString>} 0166 """ 0167 0168 ts = TextTrans() 0169 ts._init(_ctxt_, _stext_, _ptext_, kwargs) 0170 return ts.to_string() 0171 0172 0173 def t_ (_ctxt_, _text_, **kwargs): 0174 """ 0175 Get deferred translation of the text into user's language. 0176 0177 Like L{_()<_>}, but returns deferred translation object 0178 instead of translated text as string. 0179 In this way some or all arguments for named formatting directives 0180 can be supplied at a later point, using L{with_args<TextTrans.with_args>} 0181 method, and then the translated string obtained 0182 by L{to_string<TextTrans.to_string>} method. 0183 0184 @returns: deferred translation 0185 @rtype: L{TextTrans} 0186 """ 0187 0188 ts = TextTrans() 0189 ts._init(_ctxt_, _text_, None, kwargs) 0190 return ts 0191 0192 0193 def tn_ (_ctxt_, _stext_, _ptext_, **kwargs): 0194 """ 0195 Get deferred translation of the singular/plural text into user's language. 0196 0197 Like L{n_()<_>}, but returns deferred translation object 0198 instead of translated text as string. 0199 In this way some or all arguments for named formatting directives 0200 can be supplied at a later point, using L{with_args<TextTrans.with_args>} 0201 method, and then the translated string obtained 0202 by L{to_string<TextTrans.to_string>} method. 0203 0204 @returns: deferred translation 0205 @rtype: L{TextTrans} 0206 """ 0207 0208 ts = TextTrans() 0209 ts._init(_ctxt_, _stext_, _ptext_, kwargs) 0210 return ts 0211 0212 0213 class TextTrans: 0214 """ 0215 Class for intermediate handling of translated user-visible text. 0216 0217 Objects of this type are not functional if created manually, 0218 but only through C{t*_()} translation calls. 0219 """ 0220 0221 def _init (self, msgctxt, msgid, msgid_plural, kwargs): 0222 0223 self._msgctxt = msgctxt 0224 self._msgid = msgid 0225 self._msgid_plural = msgid_plural 0226 self._kwargs = kwargs 0227 0228 0229 def _copy (self): 0230 0231 # Shallow copy all attributes. 0232 t = TextTrans() 0233 t._msgctxt = self._msgctxt 0234 t._msgid = self._msgid 0235 t._msgid_plural = self._msgid_plural 0236 t._kwargs = dict(self._kwargs) 0237 return t 0238 0239 0240 def with_args (self, **kwargs): 0241 """ 0242 Add arguments for substitution in the text, creating new object. 0243 0244 @returns: new deferred translation 0245 @rtype: L{TextTrans} 0246 """ 0247 0248 t = self._copy() 0249 t._kwargs.update(kwargs) 0250 return t 0251 0252 0253 def to_string (self): 0254 """ 0255 Translate the text to get ordinary string. 0256 0257 @returns: translated text 0258 @rtype: L{ColorString<colors.ColorString>} 0259 """ 0260 0261 if self._msgid_plural is None: 0262 trf = _tr.gettext # camouflaged against xgettext 0263 if self._msgctxt is None: 0264 msgstr = trf(self._msgid) 0265 else: 0266 msgstr = trf("%s\x04%s" % (self._msgctxt, self._msgid)) 0267 if "\x04" in msgstr: 0268 msgstr = self._msgid 0269 else: 0270 n = self._kwargs.get("num") 0271 if n is None or not isinstance(n, int): 0272 raise PologyError( 0273 _("@info", 0274 "No '%(arg)s' keyword argument to " 0275 "plural translation request.", 0276 arg="num")) 0277 trf = _tr.ngettext # camouflaged against xgettext 0278 if self._msgctxt is None: 0279 msgstr = trf(self._msgid, self._msgid_plural, n) 0280 else: 0281 msgstr = trf("%s\x04%s" % (self._msgctxt, self._msgid), 0282 self._msgid_plural, n) 0283 if "\x04" in msgstr: 0284 msgstr = self._msgid 0285 0286 msgstr = ColorString(msgstr) # before substituting arguments 0287 msgstr = msgstr % self._kwargs 0288 0289 return msgstr 0290 0291 0292 class PologyError (Exception): 0293 """ 0294 Base exception class for errors in Pology. 0295 """ 0296 0297 def __init__ (self, msg): 0298 """ 0299 Constructor. 0300 0301 @param msg: a description of what went wrong 0302 @type msg: string 0303 """ 0304 0305 self._msg = msg 0306 0307 0308 def __str__(self): 0309 return str(self._msg)