summaryrefslogtreecommitdiffstats
path: root/gettext_rh.py
blob: 49ee4e3dd423ec55a9f7aa9b0530825edb13ca51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
"""This module allows python programs to use GNU gettext message catalogs.

Modified by Red Hat, Inc for use with anaconda installer.
Original file information follows:

Author: James Henstridge <james@daa.com.au>
(This is loosely based on gettext.pl in the GNU gettext distribution)

The best way to use it is like so:
    import gettext
    gettext.bindtextdomain(PACKAGE, LOCALEDIR)
    gettext.textdomain(PACKAGE)
    _ = gettext.gettext
    print _('Hello World')

where PACKAGE is the domain for this package, and LOCALEDIR is usually
'$prefix/share/locale' where $prefix is the install prefix.

If you have more than one catalog to use, you can directly create catalog
objects.  These objects are created as so:
    import gettext
    cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
    _ = cat.gettext
    print _('Hello World')

The catalog object can also be accessed as a dictionary (ie cat['hello']).

There are also some experimental features.  You can add to the catalog, just
as you would with a normal dictionary.  When you are finished, you can call
its save method, which will create a new .mo file containing all the
translations:
    import gettext
    cat = Catalog()
    cat['Hello'] = 'konichiwa'
    cat.save('./tmp.mo')

Once you have written an internationalized program, you can create a .po file
for it with "xgettext --keyword=_ fillename ...".  Then do the translation and
compile it into a .mo file, ready for use with this module.  Note that you
will have to use C style strings (ie. use double quotes) for proper string
extraction.
"""
import os, string, iutil, gzread

prefix = '/usr/local'
localedir = prefix + '/share/locale'

def _expandLang(str):
	langs = [str]
	# remove charset ...
	if '.' in str:
		langs.append(string.split(str, '.')[0])
	# also add 2 character language code ...
	if len(str) > 2:
		langs.append(str[:2])
	return langs

lang = []
for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
	if os.environ.has_key(env):
		lang = string.split(os.environ[env], ':')
		lang = map(_expandLang, lang)
		lang = reduce(lambda a, b: a + b, lang)
		break
if 'C' not in lang:
	lang.append('C')

# remove duplicates
i = 0
while i < len(lang):
	j = i + 1
	while j < len(lang):
		if lang[i] == lang[j]:
			del lang[j]
		else:
			j = j + 1
	i = i + 1
del i, j

if os.environ.has_key('PY_XGETTEXT'):
	xgettext = os.environ['PY_XGETTEXT']
else:
	xgettext = None

if iutil.getArch() == 'sparc' or iutil.getArch() == "s390":
	_gettext_byteorder = 'msb'
else:
	_gettext_byteorder = 'lsb'

del os, string, iutil

error = 'gettext.error'

def _lsbStrToInt(str):
	return ord(str[0]) + \
	       (ord(str[1]) << 8) + \
	       (ord(str[2]) << 16) + \
	       (ord(str[3]) << 24)
def _intToLsbStr(int):
	return chr(int         & 0xff) + \
	       chr((int >> 8)  & 0xff) + \
	       chr((int >> 16) & 0xff) + \
	       chr((int >> 24) & 0xff)
def _msbStrToInt(str):
	return ord(str[3]) + \
	       (ord(str[2]) << 8) + \
	       (ord(str[1]) << 16) + \
	       (ord(str[0]) << 24)
def _intToMsbStr(int):
	return chr((int >> 24) & 0xff) + \
	       chr((int >> 16) & 0xff) + \
	       chr((int >> 8) & 0xff) + \
	       chr(int & 0xff)
def _StrToInt(str):
	if _gettext_byteorder == 'msb':
		return _msbStrToInt(str)
	else:
		return _lsbStrToInt(str)
def _intToStr(int):
	if _gettext_byteorder == 'msb':
		return _intToMsbStr(str)
	else:
		return _intToLsbStr(str)

def _getpos(levels = 0):
	"""Returns the position in the code where the function was called.
	The function uses some knowledge about python stack frames."""
	import sys
	# get access to the stack frame by generating an exception.
	try:
		raise RuntimeError
	except RuntimeError:
		frame = sys.exc_traceback.tb_frame
	frame = frame.f_back # caller's frame
	while levels > 0:
		frame = frame.f_back
		levels = levels - 1
	return (frame.f_globals['__name__'],
		frame.f_code.co_name,
		frame.f_lineno)

class Catalog:
	def __init__(self, domain=None, localedir=localedir):
		self.domain = domain
		self.localedir = localedir
		self.cat = {}
		if not domain: return
		for self.lang in lang:
			if self.lang == 'C':
				return
			catalog = "%s/%s/LC_MESSAGES/%s.mo" % (
				localedir, self.lang, domain)
			try:
				f = gzread.open(catalog)
				buffer = f.read()
				f.close()
				del f
				break
			except IOError:
				pass
		else:
			return # assume C locale

		if _StrToInt(buffer[:4]) != 0x950412de:
			# magic number doesn't match
			raise error, 'Bad magic number in %s' % (catalog,)

		self.revision = _StrToInt(buffer[4:8])
		nstrings = _StrToInt(buffer[8:12])
		origTabOffset  = _StrToInt(buffer[12:16])
		transTabOffset = _StrToInt(buffer[16:20])
		for i in range(nstrings):
			origLength = _StrToInt(buffer[origTabOffset:
						      origTabOffset+4])
			origOffset = _StrToInt(buffer[origTabOffset+4:
						      origTabOffset+8])
			origTabOffset = origTabOffset + 8
			origStr = buffer[origOffset:origOffset+origLength]
		
			transLength = _StrToInt(buffer[transTabOffset:
						       transTabOffset+4])
			transOffset = _StrToInt(buffer[transTabOffset+4:
						       transTabOffset+8])
			transTabOffset = transTabOffset + 8
			transStr = buffer[transOffset:transOffset+transLength]
			
			self.cat[origStr] = transStr

	def gettext(self, string):
		"""Get the translation of a given string"""
		if self.cat.has_key(string):
			return self.cat[string]
		else:
			return string
	# allow catalog access as cat(str) and cat[str] and cat.gettext(str)
	__getitem__ = gettext
	__call__ = gettext

	# this is experimental code for producing mo files from Catalog objects
	def __setitem__(self, string, trans):
		"""Set the translation of a given string"""
		self.cat[string] = trans
	def save(self, file):
		"""Create a .mo file from a Catalog object"""
		try:
			f = open(file, "wb")
		except IOError:
			raise error, "can't open " + file + " for writing"
		f.write(_intToStr(0x950412de))    # magic number
		f.write(_intToStr(0))             # revision
		f.write(_intToStr(len(self.cat))) # nstrings

		oIndex = []; oData = ''
		tIndex = []; tData = ''
		for orig, trans in self.cat.items():
			oIndex.append((len(orig), len(oData)))
			oData = oData + orig + '\0'
			tIndex.append((len(trans), len(tData)))
			tData = tData + trans + '\0'
		oIndexOfs = 20
		tIndexOfs = oIndexOfs + 8 * len(oIndex)
		oDataOfs = tIndexOfs + 8 * len(tIndex)
		tDataOfs = oDataOfs + len(oData)
		f.write(_intToStr(oIndexOfs))
		f.write(_intToStr(tIndexOfs))
		for length, offset in oIndex:
			f.write(_intToStr(length))
			f.write(_intToStr(offset + oDataOfs))
		for length, offset in tIndex:
			f.write(_intToStr(length))
			f.write(_intToStr(offset + tDataOfs))
		f.write(oData)
		f.write(tData)

_cat = None
_cats = {}

if xgettext:
	class Catalog:
		def __init__(self, domain, localedir):
			self.domain = domain
			self.localedir = localedir
			self._strings = {}
		def gettext(self, string):
			# there is always one level of redirection for calls
			# to this function
			pos = _getpos(2) # get this function's caller
			if self._strings.has_key(string):
				if pos not in self._strings[string]:
					self._strings[string].append(pos)
			else:
				self._strings[string] = [pos]
			return string
		__getitem__ = gettext
		__call__ = gettext
		def __setitem__(self, item, data):
			pass
		def save(self, file):
			pass
		def output(self, fp):
			import string
			fp.write('# POT file for domain %s\n' % (self.domain,))
			for str in self._strings.keys():
				pos = map(lambda x: "%s(%s):%d" % x,
					  self._strings[str])
				pos.sort()
				length = 80
				for p in pos:
					if length + len(p) > 74:
						fp.write('\n#:')
						length = 2
					fp.write(' ')
					fp.write(p)
					length = length + 1 + len(p)
				fp.write('\n')
				if '\n' in str:
					fp.write('msgid ""\n')
					lines = string.split(str, '\n')
					lines = map(lambda x:
						    '"%s\\n"\n' % (x,),
						    lines[:-1]) + \
						    ['"%s"\n' % (lines[-1],)]
					fp.writelines(lines)
				else:
					fp.write('msgid "%s"\n' % (str,))
				fp.write('msgstr ""\n')
				
	import sys
	if hasattr(sys, 'exitfunc'):
		_exitchain = sys.exitfunc
	else:
		_exitchain = None
	def exitfunc(dir=xgettext, _exitchain=_exitchain):
		# actually output all the .pot files.
		import os
		for file in _cats.keys():
			fp = open(os.path.join(dir, file + '.pot'), 'w')
			cat = _cats[file]
			cat.output(fp)
			fp.close()
		if _exitchain: _exitchain()
	sys.exitfunc = exitfunc
	del sys, exitfunc, _exitchain, xgettext

def bindtextdomain(domain, localedir=localedir):
	global _cat
	if not _cats.has_key(domain):
		_cats[domain] = Catalog(domain, localedir)
	if not _cat: _cat = _cats[domain]

def textdomain(domain):
	global _cat
	if not _cats.has_key(domain):
		_cats[domain] = Catalog(domain)
	_cat = _cats[domain]

def gettext(string):
	if _cat == None: raise error, "No catalog loaded"
	return _cat.gettext(string)

_ = gettext

def dgettext(domain, string):
	if domain is None:
		return gettext(string)
	if not _cats.has_key(domain):
		raise error, "Domain '" + domain + "' not loaded"
	return _cats[domain].gettext(string)

def test():
	import sys
	global localedir
	if len(sys.argv) not in (2, 3):
		print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
		sys.exit(1)
	domain = sys.argv[1]
	if len(sys.argv) == 3:
		bindtextdomain(domain, sys.argv[2])
	textdomain(domain)
	info = gettext('')  # this is where special info is often stored
	if info:
		print "Info for domain %s, lang %s." % (domain, _cat.lang)
		print info
	else:
		print "No info given in mo file."

def getlangs():
	global lang
	return lang

def setlangs(newlang):
	global lang
	lang = newlang

if __name__ == '__main__':
	test()