]> git.lyx.org Git - features.git/blob - development/tools/mergepo.py
Use a proper command line parser
[features.git] / development / tools / mergepo.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # file mergepo.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7
8 # author Georg Baum
9
10 # Full author contact details are available in file CREDITS
11
12 # This script takes missing translations from another set of po files and
13 # merges them into the po files in this source tree.
14
15
16 import os, re, string, sys
17 import polib
18 from optparse import OptionParser
19
20
21 # we do unix/windows line trimming ourselves since it can happen that we
22 # are on unix, but the file has been written on windows or vice versa.
23 def trim_eol(line):
24     " Remove end of line char(s)."
25     if line[-2:-1] == '\r':
26         return line[:-2]
27     elif line[-1:] == '\r' or line[-1:] == '\n':
28         return line[:-1]
29     else:
30         # file with no EOL in last line
31         return line
32
33
34 def read(input):
35     " Read utf8 input file and strip lineendings."
36     lines = list()
37     while 1:
38         line = input.readline()
39         if not line:
40             break
41         line = trim_eol(line)
42         lines.append(line.decode('UTF-8'))
43     return lines
44
45
46 def parse_msg(lines):
47     " Extracts msgid or msgstr from lines."
48     if len(lines) < 1:
49         return ''
50     i = lines[0].find('"')
51     if i < 0:
52         return ''
53     msg = lines[0][i:].strip('"')
54     for i in range(1, len(lines)):
55         msg = msg + lines[i].strip('"')
56     return msg
57
58
59 def translate(msgid, msgstr_lines, po2):
60     msgstr = parse_msg(msgstr_lines)
61     if msgstr != '':
62         return 0
63     other = po2.find(msgid)
64     if not other:
65         return 0
66     if not other.translated():
67         return 0
68     msgstr = other.msgstr
69     obsolete = (msgstr_lines[0].find('#~') == 0)
70     j = msgstr_lines[0].find('"')
71     # must not assign to msgstr_lines, because that would not be seen by our caller
72     new_lines = polib.wrap(msgstr_lines[0][0:j+1] + msgstr, 76, drop_whitespace = False)
73     del msgstr_lines[:]
74     for i in range(0, len(new_lines)):
75         if i == 0:
76             msgstr_lines.append(new_lines[i] + '"')
77         elif obsolete:
78             msgstr_lines.append('#~ "' + new_lines[i] + '"')
79         else:
80             msgstr_lines.append('"' + new_lines[i] + '"')
81     return 1
82
83
84 def mergepo_polib(target, source):
85     changed = 0
86     po1 = polib.pofile(target)
87     po2 = polib.pofile(source)
88     for entry in po1.untranslated_entries():
89         other = po2.find(entry.msgid, include_obsolete_entries=True)
90         if not other:
91             continue
92         if other.translated():
93             entry.msgstr = other.msgstr
94             changed = changed + 1
95     if changed > 0:
96         po1.save(target)
97     return changed
98
99
100 def mergepo_minimaldiff(target, source):
101     changed = 0
102     po2 = polib.pofile(source)
103     target_enc = polib.detect_encoding(target)
104     # for utf8 files we can use our self written parser to minimize diffs,
105     # otherwise we need to use polib
106     if target_enc != 'UTF-8':
107         raise
108     po1 = open(target, 'rb')
109     oldlines = read(po1)
110     po1.close()
111     newlines = []
112     in_msgid = False
113     in_msgstr = False
114     msgstr_lines = []
115     msgid_lines = []
116     msgid = ''
117     for line in oldlines:
118         if in_msgid:
119             if line.find('"') == 0 or line.find('#~ "') == 0:
120                 msgid_lines.append(line)
121             else:
122                 in_msgid = False
123                 msgid = parse_msg(msgid_lines)
124                 newlines.extend(msgid_lines)
125                 msgid_lines = []
126         elif in_msgstr:
127             if line.find('"') == 0 or line.find('#~ "') == 0:
128                 msgstr_lines.append(line)
129             else:
130                 in_msgstr = False
131                 changed = changed + translate(msgid, msgstr_lines, po2)
132                 newlines.extend(msgstr_lines)
133                 msgstr_lines = []
134                 msgid = ''
135         if not in_msgid and not in_msgstr:
136             if line.find('msgid') == 0 or line.find('#~ msgid') == 0:
137                 msgid_lines.append(line)
138                 in_msgid = True
139             elif line.find('msgstr') == 0 or line.find('#~ msgstr') == 0:
140                 if line.find('msgstr[') == 0 or line.find('#~ msgstr[') == 0:
141                     # plural forms are not implemented
142                     raise
143                 msgstr_lines.append(line)
144                 in_msgstr = True
145             else:
146                 newlines.append(line)
147     if msgid != '':
148         # the file ended with a msgstr
149         changed = changed + translate(msgid, msgstr_lines, po2)
150         newlines.extend(msgstr_lines)
151         msgstr_lines = []
152         msgid = ''
153     if changed > 0:
154         # we store .po files with unix line ends in git,
155         # so do always write them even on windows
156         po1 = open(target, 'wb')
157         for line in newlines:
158             po1.write(line.encode('UTF-8') + '\n')
159     return changed
160
161
162 def mergepo(target, source):
163     if not os.path.exists(source):
164         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, source))
165         return
166     if not os.path.exists(target):
167         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
168         return
169     sys.stderr.write('Merging %s into %s: ' % (source, target))
170     try:
171         changed = mergepo_minimaldiff(target, source)
172     except:
173         changed = mergepo_polib(target, source)
174     sys.stderr.write('Updated %d translations.\n' % changed)
175
176
177 def main(argv):
178
179     parser = OptionParser(description = """This script reads translations from .po files in the given source directory
180 and adds all translations that do not already exist to the corresponding .po
181 files in the target directory. It is recommended to remerge strings from the
182 source code before running this script. Otherwise translations that are not
183 yet in the target .po files are not updated.""", usage = "Usage: %prog [options] sourcedir")
184     parser.add_option("-t", "--target", dest="target",
185                       help="target directory containing .po files. If missing, it is determined from the script location.")
186     parser.add_option("-l", "--language", dest="language",
187                       help="language for which translations are merged (if missing, all languages are merged)")
188     (options, args) = parser.parse_args(argv)
189     if len(args) <= 1:
190         parser.print_help()
191         return 0
192
193     toolsdir = os.path.dirname(args[0])
194     if options.target:
195         podir1 = os.path.abspath(options.target)
196     else:
197         podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
198     podir2 = os.path.abspath(args[1])
199
200     if options.language:
201         name = options.language + '.po'
202         mergepo(os.path.join(podir1, name), os.path.join(podir2, name))
203     else:
204         for i in os.listdir(podir1):
205             (base, ext) = os.path.splitext(i)
206             if ext != ".po":
207                 continue
208             mergepo(os.path.join(podir1, i), os.path.join(podir2, i))
209
210     return 0
211
212
213 if __name__ == "__main__":
214     main(sys.argv)