]> git.donarmstrong.com Git - lilypond.git/blob - buildscripts/gettext.py.in
e34cc77a2edff481f5858dfdee7548ab9eda8ad3
[lilypond.git] / buildscripts / gettext.py.in
1 """This module allows python programs to use GNU gettext message catalogs.
2
3 Author: James Henstridge <james@daa.com.au>
4 (This is loosely based on gettext.pl in the GNU gettext distribution)
5
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')
12
13 where PACKAGE is the domain for this package, and LOCALEDIR is usually
14 '$prefix/share/locale' where $prefix is the install prefix.
15
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')
22
23 The catalog object can also be accessed as a dictionary (ie cat['hello']).
24
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')
33
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
41
42 prefix = '/usr/local'
43 localedir = prefix + '/share/locale'
44
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
54
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')
64
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
76
77 if os.environ.has_key('PY_XGETTEXT'):
78         xgettext = os.environ['PY_XGETTEXT']
79 else:
80         xgettext = None
81
82 del os, string
83
84 error = 'gettext.error'
85
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)
101
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)
118
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
139
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,)
147
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]
159                 
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]
166                         
167                         self.cat[origStr] = transStr
168
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
178
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
192
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)
214
215 _cat = None
216 _cats = {}
217
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')
267                                 
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
284
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]
290
291 def textdomain(domain):
292         global _cat
293         if not _cats.has_key(domain):
294                 _cats[domain] = Catalog(domain)
295         _cat = _cats[domain]
296
297 def gettext(string):
298         if _cat == None: raise error, "No catalog loaded"
299         return _cat.gettext(string)
300
301 _ = gettext
302
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)
309
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."
326
327 if __name__ == '__main__':
328         test()
329