1 """This module allows python programs to use GNU gettext message catalogs.
3 Author: James Henstridge <james@daa.com.au>
4 (This is loosely based on gettext.pl in the GNU gettext distribution)
6 The best way to use it is like so:
8 gettext.bindtextdomain(PACKAGE, LOCALEDIR)
9 gettext.textdomain(PACKAGE)
11 print _('Hello World')
13 where PACKAGE is the domain for this package, and LOCALEDIR is usually
14 '$prefix/share/locale' where $prefix is the install prefix.
16 If you have more than one catalog to use, you can directly create catalog
17 objects. These objects are created as so:
19 cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
21 print _('Hello World')
23 The catalog object can also be accessed as a dictionary (ie cat['hello']).
25 There are also some experimental features. You can add to the catalog, just
26 as you would with a normal dictionary. When you are finished, you can call
27 its save method, which will create a new .mo file containing all the
31 cat['Hello'] = 'konichiwa'
34 Once you have written an internationalized program, you can create a .po file
35 for it with "xgettext --keyword=_ fillename ...". Then do the translation and
36 compile it into a .mo file, ready for use with this module. Note that you
37 will have to use C style strings (ie. use double quotes) for proper string
43 localedir = prefix + '/share/locale'
49 langs.append(string.split(str, '.')[0])
50 # also add 2 character language code ...
56 for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
57 if os.environ.has_key(env):
58 lang = string.split(os.environ[env], ':')
59 lang = map(_expandLang, lang)
60 lang = reduce(lambda a, b: a + b, lang)
70 if lang[i] == lang[j]:
77 if os.environ.has_key('PY_XGETTEXT'):
78 xgettext = os.environ['PY_XGETTEXT']
84 error = 'gettext.error'
86 def _lsbStrToInt(str):
87 return ord(str[0]) + \
88 (ord(str[1]) << 8) + \
89 (ord(str[2]) << 16) + \
91 def _msbStrToInt(str):
92 return (ord(str[0]) << 24) + \
93 (ord(str[1]) << 16) + \
94 (ord(str[2]) << 8) + \
96 def _intToLsbStr(int):
97 return chr(int & 0xff) + \
98 chr((int >> 8) & 0xff) + \
99 chr((int >> 16) & 0xff) + \
100 chr((int >> 24) & 0xff)
102 def _getpos(levels = 0):
103 """Returns the position in the code where the function was called.
104 The function uses some knowledge about python stack frames."""
106 # get access to the stack frame by generating an exception.
110 frame = sys.exc_traceback.tb_frame
111 frame = frame.f_back # caller's frame
115 return (frame.f_globals['__name__'],
116 frame.f_code.co_name,
120 def __init__(self, domain=None, localedir=localedir):
122 self.localedir = localedir
124 if not domain: return
125 for self.lang in lang:
128 catalog = "%s//%s/LC_MESSAGES/%s.mo" % (
129 localedir, self.lang, domain)
131 f = open(catalog, "rb")
138 return # assume C locale
140 strToInt = _lsbStrToInt
141 if strToInt(buffer[:4]) != 0x950412de:
142 # catalog is encoded with MSB offsets.
143 strToInt = _msbStrToInt
144 if strToInt(buffer[:4]) != 0x950412de:
145 # magic number doesn't match
146 raise error, 'Bad magic number in %s' % (catalog,)
148 self.revision = strToInt(buffer[4:8])
149 nstrings = strToInt(buffer[8:12])
150 origTabOffset = strToInt(buffer[12:16])
151 transTabOffset = strToInt(buffer[16:20])
152 for i in range(nstrings):
153 origLength = strToInt(buffer[origTabOffset:
155 origOffset = strToInt(buffer[origTabOffset+4:
157 origTabOffset = origTabOffset + 8
158 origStr = buffer[origOffset:origOffset+origLength]
160 transLength = strToInt(buffer[transTabOffset:
162 transOffset = strToInt(buffer[transTabOffset+4:
164 transTabOffset = transTabOffset + 8
165 transStr = buffer[transOffset:transOffset+transLength]
167 self.cat[origStr] = transStr
169 def gettext(self, string):
170 """Get the translation of a given string"""
171 if self.cat.has_key(string):
172 return self.cat[string]
175 # allow catalog access as cat(str) and cat[str] and cat.gettext(str)
176 __getitem__ = gettext
179 # this is experimental code for producing mo files from Catalog objects
180 def __setitem__(self, string, trans):
181 """Set the translation of a given string"""
182 self.cat[string] = trans
183 def save(self, file):
184 """Create a .mo file from a Catalog object"""
188 raise error, "can't open " + file + " for writing"
189 f.write(_intToLsbStr(0x950412de)) # magic number
190 f.write(_intToLsbStr(0)) # revision
191 f.write(_intToLsbStr(len(self.cat))) # nstrings
193 oIndex = []; oData = ''
194 tIndex = []; tData = ''
195 for orig, trans in self.cat.items():
196 oIndex.append((len(orig), len(oData)))
197 oData = oData + orig + '\0'
198 tIndex.append((len(trans), len(tData)))
199 tData = tData + trans + '\0'
201 tIndexOfs = oIndexOfs + 8 * len(oIndex)
202 oDataOfs = tIndexOfs + 8 * len(tIndex)
203 tDataOfs = oDataOfs + len(oData)
204 f.write(_intToLsbStr(oIndexOfs))
205 f.write(_intToLsbStr(tIndexOfs))
206 for length, offset in oIndex:
207 f.write(_intToLsbStr(length))
208 f.write(_intToLsbStr(offset + oDataOfs))
209 for length, offset in tIndex:
210 f.write(_intToLsbStr(length))
211 f.write(_intToLsbStr(offset + tDataOfs))
220 def __init__(self, domain, localedir):
222 self.localedir = localedir
224 def gettext(self, string):
225 # there is always one level of redirection for calls
227 pos = _getpos(2) # get this function's caller
228 if self._strings.has_key(string):
229 if pos not in self._strings[string]:
230 self._strings[string].append(pos)
232 self._strings[string] = [pos]
234 __getitem__ = gettext
236 def __setitem__(self, item, data):
238 def save(self, file):
240 def output(self, fp):
242 fp.write('# POT file for domain %s\n' % (self.domain,))
243 for str in self._strings.keys():
244 pos = map(lambda x: "%s(%s):%d" % x,
249 if length + len(p) > 74:
254 length = length + 1 + len(p)
257 fp.write('msgid ""\n')
258 lines = string.split(str, '\n')
259 lines = map(lambda x:
262 ['"%s"\n' % (lines[-1],)]
265 fp.write('msgid "%s"\n' % (str,))
266 fp.write('msgstr ""\n')
269 if hasattr(sys, 'exitfunc'):
270 _exitchain = sys.exitfunc
273 def exitfunc(dir=xgettext, _exitchain=_exitchain):
274 # actually output all the .pot files.
276 for file in _cats.keys():
277 fp = open(os.path.join(dir, file + '.pot'), 'w')
281 if _exitchain: _exitchain()
282 sys.exitfunc = exitfunc
283 del sys, exitfunc, _exitchain, xgettext
285 def bindtextdomain(domain, localedir=localedir):
287 if not _cats.has_key(domain):
288 _cats[domain] = Catalog(domain, localedir)
289 if not _cat: _cat = _cats[domain]
291 def textdomain(domain):
293 if not _cats.has_key(domain):
294 _cats[domain] = Catalog(domain)
298 if _cat == None: raise error, "No catalog loaded"
299 return _cat.gettext(string)
303 def dgettext(domain, string):
305 return gettext(string)
306 if not _cats.has_key(domain):
307 raise error, "Domain '" + domain + "' not loaded"
308 return _cats[domain].gettext(string)
313 if len(sys.argv) not in (2, 3):
314 print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
317 if len(sys.argv) == 3:
318 bindtextdomain(domain, sys.argv[2])
320 info = gettext('') # this is where special info is often stored
322 print "Info for domain %s, lang %s." % (domain, _cat.lang)
325 print "No info given in mo file."
327 if __name__ == '__main__':