Package flumotion :: Package common :: Module i18n
[hide private]

Source Code for Module flumotion.common.i18n

  1  # -*- Mode: Python; test-case-name: flumotion.test.test_i18n.py -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 17   
 18  """internationalization helpers 
 19  """ 
 20   
 21  import os 
 22  import gettext 
 23   
 24  from twisted.spread import pb 
 25   
 26  from flumotion.common import log 
 27  from flumotion.configure import configure 
 28   
 29  __version__ = "$Rev: 6693 $" 
 30   
 31   
 32  # Taken from twisted.python.util; modified so that if compareAttributes 
 33  # grows, but we get a message from a remote side that doesn't have one 
 34  # of the new attributes, that we don't raise an exception 
 35   
 36   
37 -class FancyEqMixin:
38 compareAttributes = () 39
40 - def __eq__(self, other):
41 if not self.compareAttributes: 42 return self is other 43 #XXX Maybe get rid of this, and rather use hasattr()s 44 if not isinstance(other, self.__class__): 45 return False 46 for attr in self.compareAttributes: 47 if hasattr(self, attr): 48 if not hasattr(other, attr): 49 return False 50 elif not getattr(self, attr) == getattr(other, attr): 51 return False 52 elif hasattr(other, attr): 53 return False 54 return True
55
56 - def __ne__(self, other):
57 return not self.__eq__(other)
58 59
60 -def N_(formatString):
61 """ 62 Mark a singular string for translation, without translating it. 63 """ 64 return formatString
65 66
67 -def ngettext(singular, plural, count):
68 """ 69 Mark a plural string for translation, without translating it. 70 """ 71 return (singular, plural, count)
72 73
74 -def gettexter(domain=configure.PACKAGE):
75 """ 76 Return a function that takes a format string or tuple, and additional 77 format args, 78 and creates a L{Translatable} from it. 79 80 Example:: 81 82 T_ = gettexter('flumotion') 83 t = T_(N_("Could not find '%s'."), file) 84 85 @param domain: the gettext domain to create translatables for. 86 """ 87 88 def create(formatString, *args): 89 if isinstance(formatString, str): 90 return TranslatableSingular(domain, formatString, *args) 91 else: 92 return TranslatablePlural(domain, formatString, *args)
93 94 return lambda *args: create(*args) 95 96
97 -class Translatable(pb.Copyable, pb.RemoteCopy):
98 """ 99 I represent a serializable translatable gettext msg. 100 """ 101 domain = None
102 103 # NOTE: subclassing FancyEqMixin allows us to compare two 104 # RemoteCopy instances gotten from the same Copyable; this allows 105 # state _append and _remove to work correctly 106 # Take note however that this also means that two RemoteCopy objects 107 # of two different Copyable objects, but with the same args, will 108 # also pass equality 109 # For our purposes, this is fine. 110 111
112 -class TranslatableSingular(Translatable, FancyEqMixin):
113 """ 114 I represent a translatable gettext msg in the singular form. 115 """ 116 117 compareAttributes = ["domain", "format", "args"] 118
119 - def __init__(self, domain, formatString, *args):
120 """ 121 @param domain: the text domain for translations of this message 122 @param formatString: a format string 123 @param args: any arguments to the format string 124 """ 125 self.domain = domain 126 self.format = formatString 127 self.args = args
128
129 - def untranslated(self):
130 if self.args: 131 result = self.format % self.args 132 else: 133 result = self.format 134 return result
135 pb.setUnjellyableForClass(TranslatableSingular, TranslatableSingular) 136 137
138 -class TranslatablePlural(Translatable, FancyEqMixin):
139 """ 140 I represent a translatable gettext msg in the plural form. 141 """ 142 143 compareAttributes = ["domain", "singular", "plural", "count", "args"] 144
145 - def __init__(self, domain, formatString, *args):
146 """ 147 @param domain: the text domain for translations of this message 148 @param formatString: a (singular, plural, count) tuple 149 @param args: any arguments to the format string 150 """ 151 singular, plural, count = formatString 152 self.domain = domain 153 self.singular = singular 154 self.plural = plural 155 self.count = count 156 self.args = args
157
158 - def untranslated(self):
159 if self.args: 160 result = self.singular % self.args 161 else: 162 result = self.singular 163 return result
164 pb.setUnjellyableForClass(TranslatablePlural, TranslatablePlural) 165 166
167 -class Translator(log.Loggable):
168 """ 169 I translate translatables and messages. 170 I need to be told where locale directories can be found for all domains 171 I need to translate for. 172 """ 173 174 logCategory = "translator" 175
176 - def __init__(self):
177 self._localedirs = {} # domain name -> list of locale dirs
178
179 - def addLocaleDir(self, domain, dir):
180 """ 181 Add a locale directory for the given text domain. 182 """ 183 if not domain in self._localedirs.keys(): 184 self._localedirs[domain] = [] 185 186 if not dir in self._localedirs[domain]: 187 self.debug('Adding localedir %s for domain %s' % (dir, domain)) 188 self._localedirs[domain].append(dir)
189
190 - def translateTranslatable(self, translatable, lang=None):
191 """ 192 Translate a translatable object, in the given language. 193 194 @param lang: language code (or the current locale if None) 195 """ 196 # gettext.translation objects are rumoured to be cached (API docs) 197 domain = translatable.domain 198 t = None 199 if domain in self._localedirs.keys(): 200 # FIXME: possibly trap IOError and handle nicely ? 201 for localedir in self._localedirs[domain]: 202 try: 203 t = gettext.translation(domain, localedir, lang) 204 except IOError: 205 pass 206 else: 207 self.debug('no locales for domain %s' % domain) 208 209 formatString = None 210 if not t: 211 # if no translation object found, fall back to C 212 self.debug('no translation found, falling back to C') 213 if isinstance(translatable, TranslatableSingular): 214 formatString = translatable.format 215 elif isinstance(translatable, TranslatablePlural): 216 if translatable.count == 1: 217 formatString = translatable.singular 218 else: 219 formatString = translatable.plural 220 else: 221 raise NotImplementedError('Cannot translate translatable %r' % 222 translatable) 223 else: 224 # translation object found, translate 225 if isinstance(translatable, TranslatableSingular): 226 formatString = t.gettext(translatable.format) 227 elif isinstance(translatable, TranslatablePlural): 228 formatString = t.ngettext(translatable.singular, 229 translatable.plural, 230 translatable.count) 231 else: 232 raise NotImplementedError('Cannot translate translatable %r' % 233 translatable) 234 235 if translatable.args: 236 return formatString % translatable.args 237 else: 238 return formatString
239
240 - def translate(self, message, lang=None):
241 """ 242 Translate a message, in the given language. 243 """ 244 strings = [] 245 for t in message.translatables: 246 strings.append(self.translateTranslatable(t, lang)) 247 return "".join(strings)
248 249
250 -def getLL():
251 """ 252 Return the (at most) two-letter language code set for message translation. 253 """ 254 # LANGUAGE is a GNU extension; it can be colon-seperated but we ignore the 255 # advanced stuff. If that's not present, just use LANG, as normal. 256 language = os.environ.get('LANGUAGE', None) 257 if language != None: 258 LL = language[:2] 259 else: 260 lang = os.environ.get('LANG', 'en') 261 LL = lang[:2] 262 263 return LL
264 265
266 -def installGettext():
267 """ 268 Sets up gettext so that the program gets translated. 269 Use this in any Flumotion end-user application that needs translations. 270 """ 271 import locale 272 273 localedir = os.path.join(configure.localedatadir, 'locale') 274 log.debug("locale", "Loading locales from %s" % localedir) 275 gettext.bindtextdomain(configure.PACKAGE, localedir) 276 gettext.textdomain(configure.PACKAGE) 277 # Some platforms such as win32 lacks localse.bindtextdomin/textdomain. 278 # bindtextdomain/textdomain are undocumented functions only available 279 # in the posix _locale module. We use them to avoid having to import 280 # gtk.glade here and thus import gtk/create a connection to X. 281 if hasattr(locale, 'bindtextdomain'): 282 locale.bindtextdomain(configure.PACKAGE, localedir) 283 if hasattr(locale, 'textdomain'): 284 locale.textdomain(configure.PACKAGE)
285