Merge with bs.
[wammu.git] / wammu_setup / msgfmt.py
blobfe232be4bc29e2672a65dffe1dae8ea02a995486
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
4 # Plural forms support added by alexander smishlajev <alex@tycobka.lv>
6 """Generate binary message catalog from textual translation description.
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.
12 Usage: msgfmt.py [OPTIONS] filename.po
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).
21 --help
22 Print this message and exit.
25 --version
26 Display version information and exit.
27 """
29 import sys
30 import os
31 import getopt
32 import struct
33 import array
35 __version__ = "1.1"
37 MESSAGES = {}
39 def _(text):
40 return text
42 # Just a hack to translate desktop file
43 # l10n: Name of program shown in desktop file
44 DESKTOP_NAME = _('Wammu')
45 # l10n: Generic name of program shown in desktop file
46 DESKTOP_GENERIC_NAME = _('Mobile Phone Manager')
47 # l10n: Comment about program shown in desktop file
48 DESKTOP_COMMENT = _('Application for mobile phones - frontend for Gammu')
50 DESKTOP_TRANSLATIONS = { }
52 def usage(code, msg=''):
53 print >> sys.stderr, __doc__
54 if msg:
55 print >> sys.stderr, msg
56 sys.exit(code)
60 def add(id, str, fuzzy):
61 "Add a non-fuzzy translation to the dictionary."
62 global MESSAGES
63 global DESKTOP_NAME
64 global DESKTOP_GENERIC_NAME
65 global DESKTOP_COMMENT
66 global DESKTOP_TRANSLATIONS
67 if not fuzzy and str and not str.startswith('\0'):
68 MESSAGES[id] = str
69 if id == DESKTOP_NAME:
70 DESKTOP_TRANSLATIONS['Name'] = str
71 elif id == DESKTOP_GENERIC_NAME:
72 DESKTOP_TRANSLATIONS['GenericName'] = str
73 elif id == DESKTOP_COMMENT:
74 DESKTOP_TRANSLATIONS['Comment'] = str
76 def generate():
77 "Return the generated output."
78 global MESSAGES
79 keys = MESSAGES.keys()
80 # the keys are sorted in the .mo file
81 keys.sort()
82 offsets = []
83 ids = strs = ''
84 for id in keys:
85 # For each string, we need size and file offset. Each string is NUL
86 # terminated; the NUL does not count into the size.
87 offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
88 ids += id + '\0'
89 strs += MESSAGES[id] + '\0'
90 output = ''
91 # The header is 7 32-bit unsigned integers. We don't use hash tables, so
92 # the keys start right after the index tables.
93 # translated string.
94 keystart = 7*4+16*len(keys)
95 # and the values start after the keys
96 valuestart = keystart + len(ids)
97 koffsets = []
98 voffsets = []
99 # The string table first has the list of keys, then the list of values.
100 # Each entry has first the size of the string, then the file offset.
101 for o1, l1, o2, l2 in offsets:
102 koffsets += [l1, o1+keystart]
103 voffsets += [l2, o2+valuestart]
104 offsets = koffsets + voffsets
105 output = struct.pack("Iiiiiii",
106 0x950412deL, # Magic
107 0, # Version
108 len(keys), # # of entries
109 7*4, # start of key index
110 7*4+len(keys)*8, # start of value index
111 0, 0) # size and offset of hash table
112 output += array.array("i", offsets).tostring()
113 output += ids
114 output += strs
115 return output
119 def make(filename, outfile):
120 ID = 1
121 STR = 2
122 global MESSAGES
123 global DESKTOP_TRANSLATIONS
124 MESSAGES = {}
125 DESKTOP_TRANSLATIONS = {}
127 # Compute .mo name from .po name and arguments
128 if filename.endswith('.po'):
129 infile = filename
130 else:
131 infile = filename + '.po'
132 if outfile is None:
133 outfile = os.path.splitext(infile)[0] + '.mo'
135 try:
136 lines = open(infile).readlines()
137 except IOError, msg:
138 print >> sys.stderr, msg
139 sys.exit(1)
141 section = None
142 fuzzy = 0
144 # Parse the catalog
145 lno = 0
146 for l in lines:
147 lno += 1
148 # If we get a comment line after a msgstr, this is a new entry
149 if l[0] == '#' and section == STR:
150 add(msgid, msgstr, fuzzy)
151 section = None
152 fuzzy = 0
153 # Record a fuzzy mark
154 if l[:2] == '#,' and (l.find('fuzzy') >= 0):
155 fuzzy = 1
156 # Skip comments
157 if l[0] == '#':
158 continue
159 # Start of msgid_plural section, separate from singular form with \0
160 if l.startswith('msgid_plural'):
161 msgid += '\0'
162 l = l[12:]
163 # Now we are in a msgid section, output previous section
164 elif l.startswith('msgid'):
165 if section == STR:
166 add(msgid, msgstr, fuzzy)
167 section = ID
168 l = l[5:]
169 msgid = msgstr = ''
170 # Now we are in a msgstr section
171 elif l.startswith('msgstr'):
172 section = STR
173 l = l[6:]
174 # Check for plural forms
175 if l.startswith('['):
176 # Separate plural forms with \0
177 if not l.startswith('[0]'):
178 msgstr += '\0'
179 # Ignore the index - must come in sequence
180 l = l[l.index(']') + 1:]
181 # Skip empty lines
182 l = l.strip()
183 if not l:
184 continue
185 # XXX: Does this always follow Python escape semantics?
186 l = eval(l)
187 if section == ID:
188 msgid += l
189 elif section == STR:
190 msgstr += l
191 else:
192 print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
193 'before:'
194 print >> sys.stderr, l
195 sys.exit(1)
196 # Add last entry
197 if section == STR:
198 add(msgid, msgstr, fuzzy)
200 # Compute output
201 output = generate()
203 try:
204 open(outfile,"wb").write(output)
205 except IOError,msg:
206 print >> sys.stderr, msg
210 def main():
211 try:
212 opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
213 ['help', 'version', 'output-file='])
214 except getopt.error, msg:
215 usage(1, msg)
217 outfile = None
218 # parse options
219 for opt, arg in opts:
220 if opt in ('-h', '--help'):
221 usage(0)
222 elif opt in ('-V', '--version'):
223 print >> sys.stderr, "msgfmt.py", __version__
224 sys.exit(0)
225 elif opt in ('-o', '--output-file'):
226 outfile = arg
227 # do it
228 if not args:
229 print >> sys.stderr, 'No input file given'
230 print >> sys.stderr, "Try `msgfmt --help' for more information."
231 return
233 for filename in args:
234 make(filename, outfile)
237 if __name__ == '__main__':
238 main()