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