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 -class FancyEqMixin:
40 compareAttributes = ()
41 - def __eq__(self, other):
42 if not self.compareAttributes: 43 return self is other 44 #XXX Maybe get rid of this, and rather use hasattr()s 45 if not isinstance(other, self.__class__): 46 return False 47 for attr in self.compareAttributes: 48 if hasattr(self, attr): 49 if not hasattr(other, attr): 50 return False 51 elif not getattr(self, attr) == getattr(other, attr): 52 return False 53 elif hasattr(other, attr): 54 return False 55 return True
56
57 - def __ne__(self, other):
58 return not self.__eq__(other)
59
60 -def N_(format):
61 """ 62 Mark a singular string for translation, without translating it. 63 """ 64 return format
65
66 -def ngettext(singular, plural, count):
67 """ 68 Mark a plural string for translation, without translating it. 69 """ 70 return (singular, plural, count)
71
72 -def gettexter(domain=configure.PACKAGE):
73 """ 74 Return a function that takes a format string or tuple, and additional 75 format args, 76 and creates a L{Translatable} from it. 77 78 Example:: 79 80 T_ = gettexter('flumotion') 81 t = T_(N_("Could not find '%s'."), file) 82 83 @param domain: the gettext domain to create translatables for. 84 """ 85 def create(format, *args): 86 if isinstance(format, str): 87 return TranslatableSingular(domain, format, *args) 88 else: 89 return TranslatablePlural(domain, format, *args)
90 91 return lambda *args: create(*args) 92 93
94 -class Translatable(pb.Copyable, pb.RemoteCopy):
95 """ 96 I represent a serializable translatable gettext msg. 97 """ 98 domain = None
99 100 # NOTE: subclassing FancyEqMixin allows us to compare two 101 # RemoteCopy instances gotten from the same Copyable; this allows 102 # state _append and _remove to work correctly 103 # Take note however that this also means that two RemoteCopy objects 104 # of two different Copyable objects, but with the same args, will 105 # also pass equality 106 # For our purposes, this is fine. 107
108 -class TranslatableSingular(Translatable, FancyEqMixin):
109 """ 110 I represent a translatable gettext msg in the singular form. 111 """ 112 113 compareAttributes = ["domain", "format", "args"] 114
115 - def __init__(self, domain, format, *args):
116 """ 117 @param domain: the text domain for translations of this message 118 @param format: a format string 119 @param args: any arguments to the format string 120 """ 121 self.domain = domain 122 self.format = format 123 self.args = args
124
125 - def untranslated(self):
126 if self.args: 127 result = self.format % self.args 128 else: 129 result = self.format 130 return result
131 pb.setUnjellyableForClass(TranslatableSingular, TranslatableSingular) 132 133
134 -class TranslatablePlural(Translatable, FancyEqMixin):
135 """ 136 I represent a translatable gettext msg in the plural form. 137 """ 138 139 compareAttributes = ["domain", "singular", "plural", "count", "args"] 140
141 - def __init__(self, domain, format, *args):
142 """ 143 @param domain: the text domain for translations of this message 144 @param format: a (singular, plural, count) tuple 145 @param args: any arguments to the format string 146 """ 147 singular, plural, count = format 148 self.domain = domain 149 self.singular = singular 150 self.plural = plural 151 self.count = count 152 self.args = args
153
154 - def untranslated(self):
155 if self.args: 156 result = self.singular % self.args 157 else: 158 result = self.singular 159 return result
160 pb.setUnjellyableForClass(TranslatablePlural, TranslatablePlural) 161 162
163 -class Translator(log.Loggable):
164 """ 165 I translate translatables and messages. 166 I need to be told where locale directories can be found for all domains 167 I need to translate for. 168 """ 169 170 logCategory = "translator" 171
172 - def __init__(self):
173 self._localedirs = {} # domain name -> list of locale dirs
174
175 - def addLocaleDir(self, domain, dir):
176 """ 177 Add a locale directory for the given text domain. 178 """ 179 if not domain in self._localedirs.keys(): 180 self._localedirs[domain] = [] 181 182 if not dir in self._localedirs[domain]: 183 self.debug('Adding localedir %s for domain %s' % (dir, domain)) 184 self._localedirs[domain].append(dir)
185
186 - def translateTranslatable(self, translatable, lang=None):
187 """ 188 Translate a translatable object, in the given language. 189 190 @param lang: language code (or the current locale if None) 191 """ 192 # gettext.translation objects are rumoured to be cached (API docs) 193 domain = translatable.domain 194 t = None 195 if domain in self._localedirs.keys(): 196 # FIXME: possibly trap IOError and handle nicely ? 197 for localedir in self._localedirs[domain]: 198 try: 199 t = gettext.translation(domain, localedir, lang) 200 except IOError: 201 pass 202 else: 203 self.debug('no locales for domain %s' % domain) 204 205 format = None 206 if not t: 207 # if no translation object found, fall back to C 208 self.debug('no translation found, falling back to C') 209 if isinstance(translatable, TranslatableSingular): 210 format = translatable.format 211 elif isinstance(translatable, TranslatablePlural): 212 if translatable.count == 1: 213 format = translatable.singular 214 else: 215 format = translatable.plural 216 else: 217 raise NotImplementedError('Cannot translate translatable %r' % 218 translatable) 219 else: 220 # translation object found, translate 221 if isinstance(translatable, TranslatableSingular): 222 format = t.gettext(translatable.format) 223 elif isinstance(translatable, TranslatablePlural): 224 format = t.ngettext(translatable.singular, translatable.plural, 225 translatable.count) 226 else: 227 raise NotImplementedError('Cannot translate translatable %r' % 228 translatable) 229 230 if translatable.args: 231 return format % translatable.args 232 else: 233 return format
234
235 - def translate(self, message, lang=None):
236 """ 237 Translate a message, in the given language. 238 """ 239 strings = [] 240 for t in message.translatables: 241 strings.append(self.translateTranslatable(t, lang)) 242 return "".join(strings)
243
244 -def getLL():
245 """ 246 Return the (at most) two-letter language code set for message translation. 247 """ 248 # LANGUAGE is a GNU extension; it can be colon-seperated but we ignore the 249 # advanced stuff. If that's not present, just use LANG, as normal. 250 language = os.environ.get('LANGUAGE', None) 251 if language != None: 252 LL = language[:2] 253 else: 254 lang = os.environ.get('LANG', 'en') 255 LL = lang[:2] 256 257 return LL
258
259 -def installGettext():
260 """ 261 Sets up gettext so that the program gets translated. 262 Use this in any Flumotion end-user application that needs translations. 263 """ 264 import locale 265 266 localedir = os.path.join(configure.localedatadir, 'locale') 267 log.debug("locale", "Loading locales from %s" % localedir) 268 gettext.bindtextdomain(configure.PACKAGE, localedir) 269 gettext.textdomain(configure.PACKAGE) 270 locale.bindtextdomain(configure.PACKAGE, localedir) 271 locale.textdomain(configure.PACKAGE)
272