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__':