Package dogtail :: Module i18n
[hide private]
[frames] | no frames]

Source Code for Module dogtail.i18n

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Internationalization facilities 
  4   
  5  Authors: David Malcolm <dmalcolm@redhat.com> 
  6  """ 
  7   
  8  __author__ = """David Malcolm <dmalcolm@redhat.com>, Zack Cerza <zcerza@redhat.com>""" 
  9   
 10  import config 
 11   
 12  import os 
 13  import re 
 14  import gettext 
 15   
 16  from logging import debugLogger as logger 
 17  from __builtin__ import unicode 
 18   
 19   
20 -def safeDecode(string):
21 try: 22 string = string.decode('utf-8', 'replace') 23 except UnicodeEncodeError: 24 string = string.encode('utf-8', 'replace') 25 return string
26 27
28 -def safeEncode(string):
29 pass
30 31 32 """ 33 Singleton list of TranslationDb instances, to be initialized by the script with 34 whatever translation databases it wants. 35 """ 36 translationDbs = [] 37 38
39 -class TranslationDb(object):
40 41 """ 42 Abstract base class representing a database of translations 43 """ 44
45 - def getTranslationsOf(self, srcName):
46 """ 47 Pure virtual method to look up the translation of a string. 48 Returns a list of candidate strings (the translation), empty if not found. 49 50 Note that a source string can map to multiple translated strings. For 51 example, in the French translation of Evolution, the string "Forward" can 52 translate to both 53 (i) "Faire suivre" for forwarding an email, and 54 (ii) "Suivant" for the next page in a wizard. 55 """ 56 raise NotImplementedError
57 58
59 -class GettextTranslationDb(TranslationDb):
60 61 """ 62 Implementation of TranslationDb which leverages gettext, using a single 63 translation mo-file. 64 """ 65
66 - def __init__(self, moFile):
67 self.__moFile = moFile 68 self.__gnutranslations = gettext.GNUTranslations(open(moFile))
69
70 - def getTranslationsOf(self, srcName):
71 srcName = safeDecode(srcName) 72 # print "searching for translations of %s"%srcName 73 # Use a dict to get uniqueness: 74 results = {} 75 result = self.__gnutranslations.ugettext(srcName) 76 if result != srcName: 77 results[result] = None 78 79 # Hack alert: 80 # 81 # Note that typical UI definition in GTK etc contains strings with 82 # underscores to denote accelerators. 83 # For example, the stock GTK "Add" item has text "_Add" which e.g. 84 # translates to "A_jouter" in French 85 # 86 # Since these underscores have been stripped out before we see these strings, 87 # we are looking for a translation of "Add" into "Ajouter" in this case, so 88 # we need to fake it, by looking up the string multiple times, with underscores 89 # inserted in all possible positions, stripping underscores out of the result. 90 # Ugly, but it works. 91 92 for index in range(len(srcName)): 93 candidate = srcName[:index] + "_" + srcName[index:] 94 result = self.__gnutranslations.ugettext(candidate) 95 if result != candidate: 96 # Strip out the underscore, and add to the result: 97 results[result.replace('_', '')] = True 98 99 return results.keys()
100 101
102 -def translate(srcString):
103 """ 104 Look up srcString in the various translation databases (if any), returning 105 a list of all matches found (potentially the empty list) 106 """ 107 # Use a dict to get uniqueness: 108 results = {} 109 # Try to translate the string: 110 for translationDb in translationDbs: 111 for result in translationDb.getTranslationsOf(srcString): 112 result = safeDecode(result) 113 results[result] = True 114 115 # No translations found: 116 if len(results) == 0: 117 if config.config.debugTranslation: 118 logger.log('Translation not found for "%s"' % srcString) 119 return results.keys()
120 121
122 -class TranslatableString(object):
123 124 """ 125 Class representing a string that we want to match strings against, handling 126 translation for us, by looking it up once at construction time. 127 """ 128
129 - def __init__(self, untranslatedString):
130 """ 131 Constructor looks up the string in all of the translation databases, storing 132 the various translations it finds. 133 """ 134 untranslatedString = safeDecode(untranslatedString) 135 self.untranslatedString = untranslatedString 136 self.translatedStrings = translate(untranslatedString)
137
138 - def matchedBy(self, string):
139 """ 140 Compare the test string against either the translation of the original 141 string (or simply the original string, if no translation was found). 142 """ 143 # print "comparing %s against %s"%(string, self) 144 def stringsMatch(inS, outS): 145 """ 146 Compares a regular expression to a string 147 148 inS: the regular expression (or normal string) 149 outS: the normal string to be compared against 150 """ 151 inString = str(inS) 152 outString = outS 153 if inString == outString: 154 return True 155 inString = inString + '$' 156 inString = safeDecode(inString) 157 outString = safeDecode(outString) 158 if inString[0] == '*': 159 inString = "\\" + inString 160 # Escape all parentheses, since grouping will never be needed here 161 inString = re.sub('([\(\)])', r'\\\1', inString) 162 match = re.match(inString, outString) 163 matched = match is not None 164 return matched
165 166 matched = False 167 # the 'ts' variable keeps track of whether we're working with 168 # translated strings. it's only used for debugging purposes. 169 #ts = 0 170 # print string, str(self) 171 for translatedString in self.translatedStrings: 172 #ts = ts + 1 173 matched = stringsMatch(translatedString, string) 174 if not matched: 175 matched = translatedString == string 176 if matched: 177 return matched 178 # ts=0 179 return stringsMatch(self.untranslatedString, string)
180
181 - def __str__(self):
182 """ 183 Provide a meaningful debug version of the string (and the translation in 184 use) 185 """ 186 if len(self.translatedStrings) > 0: 187 # build an output string, with commas in the correct places 188 translations = "" 189 for tString in self.translatedStrings: 190 translations += u'"%s", ' % safeDecode(tString) 191 result = u'"%s" (%s)' % ( 192 safeDecode(self.untranslatedString), translations) 193 return safeDecode(result) 194 else: 195 return '"%s"' % (self.untranslatedString)
196 197
198 -def isMoFile(filename, language=''):
199 """ 200 Does the given filename look like a gettext mo file? 201 202 Optionally: Does the file also contain translations for a certain language, 203 for example 'ja'? 204 """ 205 if re.match('(.*)\\.mo$', filename): 206 if not language: 207 return True 208 elif re.match('/usr/share/locale(.*)/%s(.*)/LC_MESSAGES/(.*)\\.mo$' % 209 language, filename): 210 return True 211 else: 212 return False 213 else: 214 return False
215 216
217 -def loadAllTranslationsForLanguage(language):
218 import distro 219 for moFile in distro.packageDb.getMoFiles(language): 220 translationDbs.append(GettextTranslationDb(moFile))
221 222
223 -def getMoFilesForPackage(packageName, language='', getDependencies=True):
224 """ 225 Look up the named package and find all gettext mo files within it and its 226 dependencies. It is possible to restrict the results to those of a certain 227 language, for example 'ja'. 228 """ 229 import distro 230 231 result = [] 232 for filename in distro.packageDb.getFiles(packageName): 233 if isMoFile(filename, language): 234 result.append(filename) 235 236 if getDependencies: 237 # Recurse: 238 for dep in distro.packageDb.getDependencies(packageName): 239 # We pass False to the inner call because getDependencies has already 240 # walked the full tree 241 result.extend(getMoFilesForPackage(dep, language, False)) 242 243 return result
244 245
246 -def loadTranslationsFromPackageMoFiles(packageName, getDependencies=True):
247 """ 248 Helper function which appends all of the gettext translation mo-files used by 249 the package (and its dependencies) to the translation database list. 250 """ 251 # Keep a list of mo-files that are already in use to avoid duplicates. 252 moFiles = {} 253 254 def load(packageName, language='', getDependencies=True): 255 for moFile in getMoFilesForPackage(packageName, language, getDependencies): 256 # Searching the popt mo-files for translations makes gettext bail out, 257 # so we ignore them here. This is 258 # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 . 259 if not('popt.mo' in moFile or moFile in moFiles): 260 try: 261 translationDbs.append(GettextTranslationDb(moFile)) 262 moFiles[moFile] = None 263 except (AttributeError, IndexError): 264 if config.config.debugTranslation: 265 #import traceback 266 # logger.log(traceback.format_exc()) 267 logger.log( 268 "Warning: Failed to load mo-file for translation: " + moFile)
269 270 # Hack alert: 271 # 272 # The following special-case is necessary for Ubuntu, since their 273 # translations are shipped in a single huge package. The downside to 274 # this special case, aside from the simple fact that there is one, 275 # is that it makes automatic translations much slower. 276 277 import distro 278 language = os.environ.get('LANGUAGE', os.environ['LANG'])[0:2] 279 if isinstance(distro.distro, distro.Ubuntu): 280 load('language-pack-gnome-%s' % language, language) 281 load(packageName, language, getDependencies) 282