blob: aa72a075ecf49cb8cab010a02e08a44d282cd146 [file] [log] [blame]
Barry Warsaw72dacb82000-09-01 08:10:08 +00001#! /usr/bin/env python
2
3# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
4
5"""Generate binary message catalog from textual translation description.
6
7This program converts a textual Uniforum-style message catalog (.po file) into
8a binary GNU catalog (.mo file). This is essentially the same function as the
9GNU msgfmt program, however, it is a simpler implementation.
10
11Usage: msgfmt.py [OPTIONS] filename.po
12
13Options:
14 -h
15 --help
16 Print this message and exit.
17
18 -V
19 --version
20 Display version information and exit.
21
22"""
23
24import sys
25import getopt
26import struct
27import array
28
29__version__ = "1.0"
30
31MESSAGES = {}
32
33
34
35def usage(code, msg=''):
36 print >> sys.stderr, __doc__
37 if msg:
38 print >> sys.stderr, msg
39 sys.exit(code)
40
41
42
43def add(id, str, fuzzy):
44 "Add a non-fuzzy translation to the dictionary."
45 global MESSAGES
46 if not fuzzy and str:
47 MESSAGES[id] = str
48
49
50
51def generate():
52 "Return the generated output."
53 global MESSAGES
54 keys = MESSAGES.keys()
55 # the keys are sorted in the .mo file
56 keys.sort()
57 offsets = []
58 ids = strs = ''
59 for id in keys:
60 # For each string, we need size and file offset. Each string is NUL
61 # terminated; the NUL does not count into the size.
62 offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
63 ids += id + '\0'
64 strs += MESSAGES[id] + '\0'
65 output = ''
66 # The header is 7 32-bit unsigned integers. We don't use hash tables, so
67 # the keys start right after the index tables.
68 # translated string.
69 keystart = 7*4+16*len(keys)
70 # and the values start after the keys
71 valuestart = keystart + len(ids)
72 koffsets = []
73 voffsets = []
74 # The string table first has the list of keys, then the list of values.
75 # Each entry has first the size of the string, then the file offset.
76 for o1, l1, o2, l2 in offsets:
77 koffsets += [l1, o1+keystart]
78 voffsets += [l2, o2+valuestart]
79 offsets = koffsets + voffsets
80 output = struct.pack("iiiiiii",
81 0x950412de, # Magic
82 0, # Version
83 len(keys), # # of entries
84 7*4, # start of key index
85 7*4+len(keys)*8, # start of value index
86 0, 0) # size and offset of hash table
87 output += array.array("i", offsets).tostring()
88 output += ids
89 output += strs
90 return output
91
92
93
94def make(filename):
95 ID = 1
96 STR = 2
97
98 # Compute .mo name from .po name
99 if filename.endswith('.po'):
100 infile = filename
101 outfile = filename[:-2] + 'mo'
102 else:
103 infile = filename + '.po'
104 outfile = filename + '.mo'
105 try:
106 lines = open(infile).readlines()
107 except IOError, msg:
108 print >> sys.stderr, msg
109 sys.exit(1)
110
111 section = None
112 fuzzy = 0
113
114 # Parse the catalog
115 lno = 0
116 for l in lines:
117 lno += 1
118 # If we get a comment line after a msgstr, this is a new entry
119 if l[0] == '#' and section == STR:
120 add(msgid, msgstr, fuzzy)
121 section = None
122 fuzzy = 0
123 # Record a fuzzy mark
124 if l[:2] == '#,' and l.find('fuzzy'):
125 fuzzy = 1
126 # Skip comments
127 if l[0] == '#':
128 continue
129 # Now we are in a msgid section, output previous section
130 if l.startswith('msgid'):
131 if section == STR:
132 add(msgid, msgstr, fuzzy)
133 section = ID
134 l = l[5:]
135 msgid = msgstr = ''
136 # Now we are in a msgstr section
137 elif l.startswith('msgstr'):
138 section = STR
139 l = l[6:]
140 # Skip empty lines
141 l = l.strip()
142 if not l:
143 continue
144 # XXX: Does this always follow Python escape semantics?
145 l = eval(l)
146 if section == ID:
147 msgid += l
148 elif section == STR:
149 msgstr += l
150 else:
151 print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
152 'before:'
153 print >> sys.stderr, l
154 sys.exit(1)
155 # Add last entry
156 if section == STR:
157 add(msgid, msgstr, fuzzy)
158
159 # Compute output
160 output = generate()
161
162 # Save output
163 try:
164 open(outfile,"wb").write(output)
165 except IOError,msg:
166 print >> sys.stderr, msg
167
168
169
170def main():
171 try:
172 opts, args = getopt.getopt(sys.argv[1:], 'hV', ['help','version'])
173 except getopt.error, msg:
174 usage(1, msg)
175
176 # parse options
177 for opt, arg in opts:
178 if opt in ('-h', '--help'):
179 usage(0)
180 elif opt in ('-V', '--version'):
181 print >> sys.stderr, "msgfmt.py", __version__
182 sys.exit(0)
183 # do it
184 if not args:
185 print >> sys.stderr, 'No input file given'
186 print >> sys.stderr, "Try `msgfmt --help' for more information."
187 return
188
189 for filename in args:
190 make(filename)
191
192
193if __name__ == '__main__':
194 main()