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