comparison MoinMoin/i18n/msgfmt.py @ 896:9dcd18a790ab

i18n: no .mo files anymore, we directly read .po and cache the result
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Fri, 07 Jul 2006 12:29:58 +0200
parents
children 6dd2e29acffe
comparison
equal deleted inserted replaced
895:eb465f4d2af0 896:9dcd18a790ab
1 #!/usr/bin/env python
2 # -*- coding: iso-8859-1 -*-
3 """Generate binary message catalog from textual translation description.
4
5 This program converts a textual Uniforum-style message catalog (.po file) into
6 a binary GNU catalog (.mo file). This is essentially the same function as the
7 GNU msgfmt program, however, it is a simpler implementation.
8
9 Usage: msgfmt.py [OPTIONS] filename.po
10
11 Options:
12 -o file
13 --output-file=file
14 Specify the output file to write to. If omitted, output will go to a
15 file named filename.mo (based off the input file name).
16
17 -h
18 --help
19 Print this message and exit.
20
21 -V
22 --version
23 Display version information and exit.
24
25 Written by Martin v. L÷wis <loewis@informatik.hu-berlin.de>,
26 refactored by Thomas Waldmann <tw AT waldmann-edv DOT de>.
27 """
28
29 import sys, os
30 import getopt, struct, array
31
32 __version__ = "1.2"
33
34 class SyntaxErrorException(Exception):
35 """raised when having trouble parsing the po file content"""
36 pass
37
38 class MsgFmt(object):
39 """transform .po -> .mo format"""
40 def __init__(self):
41 self.messages = {}
42
43 def make_filenames(self, filename, outfile=None):
44 """Compute .mo name from .po name or language"""
45 if filename.endswith('.po'):
46 infile = filename
47 else:
48 infile = filename + '.po'
49 if outfile is None:
50 outfile = os.path.splitext(infile)[0] + '.mo'
51 return infile, outfile
52
53 def add(self, id, str, fuzzy):
54 """Add a non-fuzzy translation to the dictionary."""
55 if not fuzzy and str:
56 self.messages[id] = str
57
58 def read_po(self, lines):
59 ID = 1
60 STR = 2
61 section = None
62 fuzzy = False
63 line_no = 0
64 # Parse the catalog
65 for line in lines:
66 line_no += 1
67 # If we get a comment line after a msgstr, this is a new entry
68 if line.startswith('#') and section == STR:
69 self.add(msgid, msgstr, fuzzy)
70 section = None
71 fuzzy = False
72 # Record a fuzzy mark
73 if line.startswith('#,') and 'fuzzy' in line:
74 fuzzy = True
75 # Skip comments
76 if line.startswith('#'):
77 continue
78 # Now we are in a msgid section, output previous section
79 if line.startswith('msgid'):
80 if section == STR:
81 self.add(msgid, msgstr, fuzzy)
82 section = ID
83 line = line[5:]
84 msgid = msgstr = ''
85 # Now we are in a msgstr section
86 elif line.startswith('msgstr'):
87 section = STR
88 line = line[6:]
89 # Skip empty lines
90 line = line.strip()
91 if not line:
92 continue
93 # XXX: Does this always follow Python escape semantics?
94 line = eval(line)
95 if section == ID:
96 msgid += line
97 elif section == STR:
98 msgstr += line
99 else:
100 raise SyntaxErrorException('Syntax error on %s:%d, before:\n%s' % (infile, line_no, line))
101 # Add last entry
102 if section == STR:
103 self.add(msgid, msgstr, fuzzy)
104
105 def generate_mo(self):
106 """Return the generated output."""
107 keys = self.messages.keys()
108 # the keys are sorted in the .mo file
109 keys.sort()
110 offsets = []
111 ids = ''
112 strs = ''
113 for id in keys:
114 # For each string, we need size and file offset. Each string is NUL
115 # terminated; the NUL does not count into the size.
116 offsets.append((len(ids), len(id), len(strs), len(self.messages[id])))
117 ids += id + '\0'
118 strs += self.messages[id] + '\0'
119 output = []
120 # The header is 7 32-bit unsigned integers. We don't use hash tables, so
121 # the keys start right after the index tables.
122 # translated string.
123 keystart = 7*4 + 16*len(keys)
124 # and the values start after the keys
125 valuestart = keystart + len(ids)
126 koffsets = []
127 voffsets = []
128 # The string table first has the list of keys, then the list of values.
129 # Each entry has first the size of the string, then the file offset.
130 for o1, l1, o2, l2 in offsets:
131 koffsets += [l1, o1 + keystart]
132 voffsets += [l2, o2 + valuestart]
133 offsets = koffsets + voffsets
134 output.append(struct.pack("Iiiiiii",
135 0x950412deL, # Magic
136 0, # Version
137 len(keys), # # of entries
138 7*4, # start of key index
139 7*4 + len(keys)*8, # start of value index
140 0, 0)) # size and offset of hash table
141 output.append(array.array("i", offsets).tostring())
142 output.append(ids)
143 output.append(strs)
144 return ''.join(output)
145
146
147 def make(filename, outfile):
148 mf = MsgFmt()
149 infile, outfile = mf.make_filenames(filename, outfile)
150 try:
151 lines = file(infile).readlines()
152 except IOError, msg:
153 print >> sys.stderr, msg
154 sys.exit(1)
155 try:
156 mf.read_po(lines)
157 output = mf.generate_mo()
158 except SyntaxErrorException, msg:
159 print >> sys.stderr, msg
160
161 try:
162 open(outfile, "wb").write(output)
163 except IOError, msg:
164 print >> sys.stderr, msg
165
166
167 def usage(code, msg=''):
168 print >> sys.stderr, __doc__
169 if msg:
170 print >> sys.stderr, msg
171 sys.exit(code)
172
173
174 def main():
175 try:
176 opts, args = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version', 'output-file='])
177 except getopt.error, msg:
178 usage(1, msg)
179
180 outfile = None
181 # parse options
182 for opt, arg in opts:
183 if opt in ('-h', '--help'):
184 usage(0)
185 elif opt in ('-V', '--version'):
186 print >> sys.stderr, "msgfmt.py", __version__
187 sys.exit(0)
188 elif opt in ('-o', '--output-file'):
189 outfile = arg
190 # do it
191 if not args:
192 print >> sys.stderr, 'No input file given'
193 print >> sys.stderr, "Try `msgfmt --help' for more information."
194 return
195
196 for filename in args:
197 make(filename, outfile)
198
199
200 if __name__ == '__main__':
201 main()
202