lilypond-1.3.145
[lilypond.git] / buildscripts / gettext.py.in
blobe34cc77a2edff481f5858dfdee7548ab9eda8ad3
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:
7 import gettext
8 gettext.bindtextdomain(PACKAGE, LOCALEDIR)
9 gettext.textdomain(PACKAGE)
10 _ = gettext.gettext
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:
18 import gettext
19 cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
20 _ = cat.gettext
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
28 translations:
29 import gettext
30 cat = Catalog()
31 cat['Hello'] = 'konichiwa'
32 cat.save('./tmp.mo')
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
38 extraction.
39 """
40 import os, string
42 prefix = '/usr/local'
43 localedir = prefix + '/share/locale'
45 def _expandLang(str):
46 langs = [str]
47 # remove charset ...
48 if '.' in str:
49 langs.append(string.split(str, '.')[0])
50 # also add 2 character language code ...
51 if len(str) > 2:
52 langs.append(str[:2])
53 return langs
55 lang = []
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)
61 break
62 if 'C' not in lang:
63 lang.append('C')
65 # remove duplicates
66 i = 0
67 while i < len(lang):
68 j = i + 1
69 while j < len(lang):
70 if lang[i] == lang[j]:
71 del lang[j]
72 else:
73 j = j + 1
74 i = i + 1
75 del i, j
77 if os.environ.has_key('PY_XGETTEXT'):
78 xgettext = os.environ['PY_XGETTEXT']
79 else:
80 xgettext = None
82 del os, string
84 error = 'gettext.error'
86 def _lsbStrToInt(str):
87 return ord(str[0]) + \
88 (ord(str[1]) << 8) + \
89 (ord(str[2]) << 16) + \
90 (ord(str[3]) << 24)
91 def _msbStrToInt(str):
92 return (ord(str[0]) << 24) + \
93 (ord(str[1]) << 16) + \
94 (ord(str[2]) << 8) + \
95 ord(str[3])
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."""
105 import sys
106 # get access to the stack frame by generating an exception.
107 try:
108 raise RuntimeError
109 except RuntimeError:
110 frame = sys.exc_traceback.tb_frame
111 frame = frame.f_back # caller's frame
112 while levels > 0:
113 frame = frame.f_back
114 levels = levels - 1
115 return (frame.f_globals['__name__'],
116 frame.f_code.co_name,
117 frame.f_lineno)
119 class Catalog:
120 def __init__(self, domain=None, localedir=localedir):
121 self.domain = domain
122 self.localedir = localedir
123 self.cat = {}
124 if not domain: return
125 for self.lang in lang:
126 if self.lang == 'C':
127 return
128 catalog = "%s//%s/LC_MESSAGES/%s.mo" % (
129 localedir, self.lang, domain)
130 try:
131 f = open(catalog, "rb")
132 buffer = f.read()
133 del f
134 break
135 except IOError:
136 pass
137 else:
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:
154 origTabOffset+4])
155 origOffset = strToInt(buffer[origTabOffset+4:
156 origTabOffset+8])
157 origTabOffset = origTabOffset + 8
158 origStr = buffer[origOffset:origOffset+origLength]
160 transLength = strToInt(buffer[transTabOffset:
161 transTabOffset+4])
162 transOffset = strToInt(buffer[transTabOffset+4:
163 transTabOffset+8])
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]
173 else:
174 return string
175 # allow catalog access as cat(str) and cat[str] and cat.gettext(str)
176 __getitem__ = gettext
177 __call__ = 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"""
185 try:
186 f = open(file, "wb")
187 except IOError:
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'
200 oIndexOfs = 20
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))
212 f.write(oData)
213 f.write(tData)
215 _cat = None
216 _cats = {}
218 if xgettext:
219 class Catalog:
220 def __init__(self, domain, localedir):
221 self.domain = domain
222 self.localedir = localedir
223 self._strings = {}
224 def gettext(self, string):
225 # there is always one level of redirection for calls
226 # to this function
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)
231 else:
232 self._strings[string] = [pos]
233 return string
234 __getitem__ = gettext
235 __call__ = gettext
236 def __setitem__(self, item, data):
237 pass
238 def save(self, file):
239 pass
240 def output(self, fp):
241 import string
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,
245 self._strings[str])
246 pos.sort()
247 length = 80
248 for p in pos:
249 if length + len(p) > 74:
250 fp.write('\n#:')
251 length = 2
252 fp.write(' ')
253 fp.write(p)
254 length = length + 1 + len(p)
255 fp.write('\n')
256 if '\n' in str:
257 fp.write('msgid ""\n')
258 lines = string.split(str, '\n')
259 lines = map(lambda x:
260 '"%s\\n"\n' % (x,),
261 lines[:-1]) + \
262 ['"%s"\n' % (lines[-1],)]
263 fp.writelines(lines)
264 else:
265 fp.write('msgid "%s"\n' % (str,))
266 fp.write('msgstr ""\n')
268 import sys
269 if hasattr(sys, 'exitfunc'):
270 _exitchain = sys.exitfunc
271 else:
272 _exitchain = None
273 def exitfunc(dir=xgettext, _exitchain=_exitchain):
274 # actually output all the .pot files.
275 import os
276 for file in _cats.keys():
277 fp = open(os.path.join(dir, file + '.pot'), 'w')
278 cat = _cats[file]
279 cat.output(fp)
280 fp.close()
281 if _exitchain: _exitchain()
282 sys.exitfunc = exitfunc
283 del sys, exitfunc, _exitchain, xgettext
285 def bindtextdomain(domain, localedir=localedir):
286 global _cat
287 if not _cats.has_key(domain):
288 _cats[domain] = Catalog(domain, localedir)
289 if not _cat: _cat = _cats[domain]
291 def textdomain(domain):
292 global _cat
293 if not _cats.has_key(domain):
294 _cats[domain] = Catalog(domain)
295 _cat = _cats[domain]
297 def gettext(string):
298 if _cat == None: raise error, "No catalog loaded"
299 return _cat.gettext(string)
301 _ = gettext
303 def dgettext(domain, string):
304 if domain is None:
305 return gettext(string)
306 if not _cats.has_key(domain):
307 raise error, "Domain '" + domain + "' not loaded"
308 return _cats[domain].gettext(string)
310 def test():
311 import sys
312 global localedir
313 if len(sys.argv) not in (2, 3):
314 print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
315 sys.exit(1)
316 domain = sys.argv[1]
317 if len(sys.argv) == 3:
318 bindtextdomain(domain, sys.argv[2])
319 textdomain(domain)
320 info = gettext('') # this is where special info is often stored
321 if info:
322 print "Info for domain %s, lang %s." % (domain, _cat.lang)
323 print info
324 else:
325 print "No info given in mo file."
327 if __name__ == '__main__':
328 test()