]> git.lyx.org Git - features.git/blob - development/tools/mergepo.py
Check that we have bash before running updatelfuns.sh
[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         j = lines[i].find('"')
56         if j < 0:
57             return ''
58         msg = msg + lines[i][j:].strip('"')
59     return polib.unescape(msg)
60
61
62 def translate(msgid, msgstr_lines, po2, options):
63     msgstr = parse_msg(msgstr_lines)
64     if options.overwrite:
65         other = po2.find(msgid)
66         if not other:
67             return 0
68         if not other.translated():
69             return 0
70         if msgstr == other.msgstr:
71             return 0
72     else:
73         if msgstr != '':
74             return 0
75         other = po2.find(msgid)
76         if not other:
77             return 0
78         if not other.translated():
79             return 0
80     if options.nonnull and other.msgstr == other.msgid:
81         return 0
82     msgstr = other.msgstr
83     obsolete = (msgstr_lines[0].find('#~') == 0)
84     j = msgstr_lines[0].find('"')
85     # must not assign to msgstr_lines, because that would not be seen by our caller
86     new_lines = polib.wrap(msgstr_lines[0][0:j+1] + polib.escape(msgstr), 76, drop_whitespace = False)
87     del msgstr_lines[:]
88     for i in range(0, len(new_lines)):
89         if i == 0:
90             msgstr_lines.append(new_lines[i] + '"')
91         elif obsolete:
92             msgstr_lines.append('#~ "' + new_lines[i] + '"')
93         else:
94             msgstr_lines.append('"' + new_lines[i] + '"')
95     return 1
96
97
98 def mergepo_polib(target, source, options):
99     changed = 0
100     po1 = polib.pofile(target)
101     po2 = polib.pofile(source)
102     if options.overwrite:
103         for entry in po1.entries():
104             other = po2.find(entry.msgid, include_obsolete_entries=True)
105             if not other:
106                 continue
107             if options.nonnull and other.msgstr == other.msgid:
108                 continue
109             if other.translated() and other.msgstr != entry.msgstr:
110                 entry.msgstr = other.msgstr
111                 changed = changed + 1
112     else:
113         for entry in po1.untranslated_entries():
114             other = po2.find(entry.msgid, include_obsolete_entries=True)
115             if not other:
116                 continue
117             if options.nonnull and other.msgstr == other.msgid:
118                 continue
119             if other.translated():
120                 entry.msgstr = other.msgstr
121                 changed = changed + 1
122     if changed > 0:
123         po1.save(target)
124     return changed
125
126
127 def mergepo_minimaldiff(target, source, options):
128     changed = 0
129     po2 = polib.pofile(source)
130     target_enc = polib.detect_encoding(target)
131     # for utf8 files we can use our self written parser to minimize diffs,
132     # otherwise we need to use polib
133     if target_enc != 'UTF-8':
134         raise
135     po1 = open(target, 'rb')
136     oldlines = read(po1)
137     po1.close()
138     newlines = []
139     in_msgid = False
140     in_msgstr = False
141     msgstr_lines = []
142     msgid_lines = []
143     msgid = ''
144     for line in oldlines:
145         if in_msgid:
146             if line.find('"') == 0 or line.find('#~ "') == 0:
147                 msgid_lines.append(line)
148             else:
149                 in_msgid = False
150                 msgid = parse_msg(msgid_lines)
151                 newlines.extend(msgid_lines)
152                 msgid_lines = []
153         elif in_msgstr:
154             if line.find('"') == 0 or line.find('#~ "') == 0:
155                 msgstr_lines.append(line)
156             else:
157                 in_msgstr = False
158                 changed = changed + translate(msgid, msgstr_lines, po2, options)
159                 newlines.extend(msgstr_lines)
160                 msgstr_lines = []
161                 msgid = ''
162         if not in_msgid and not in_msgstr:
163             if line.find('msgid') == 0 or line.find('#~ msgid') == 0:
164                 msgid_lines.append(line)
165                 in_msgid = True
166             elif line.find('msgstr') == 0 or line.find('#~ msgstr') == 0:
167                 if line.find('msgstr[') == 0 or line.find('#~ msgstr[') == 0:
168                     # plural forms are not implemented
169                     raise
170                 msgstr_lines.append(line)
171                 in_msgstr = True
172             else:
173                 newlines.append(line)
174     if msgid != '':
175         # the file ended with a msgstr
176         changed = changed + translate(msgid, msgstr_lines, po2, options)
177         newlines.extend(msgstr_lines)
178         msgstr_lines = []
179         msgid = ''
180     if changed > 0:
181         # we store .po files with unix line ends in git,
182         # so do always write them even on windows
183         po1 = open(target, 'wb')
184         for line in newlines:
185             po1.write(line.encode('UTF-8') + '\n')
186     return changed
187
188
189 def mergepo(target, source, options):
190     if not os.path.exists(source):
191         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, source))
192         return
193     if not os.path.exists(target):
194         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
195         return
196     sys.stderr.write('Merging %s into %s: ' % (source, target))
197     try:
198         changed = mergepo_minimaldiff(target, source, options)
199         sys.stderr.write('Updated %d translations with minimal diff.\n' % changed)
200     except:
201         changed = mergepo_polib(target, source, options)
202         sys.stderr.write('Updated %d translations using polib.\n' % changed)
203
204
205 def main(argv):
206
207     parser = OptionParser(description = """This script reads translations from .po files in the given source directory
208 and adds all translations that do not already exist to the corresponding .po
209 files in the target directory. It is recommended to remerge strings from the
210 source code before running this script. Otherwise translations that are not
211 yet in the target .po files are not updated.""", usage = "Usage: %prog [options] sourcedir")
212     parser.add_option("-t", "--target", dest="target",
213                       help="target directory containing .po files. If missing, it is determined from the script location.")
214     parser.add_option("-l", "--language", dest="language",
215                       help="language for which translations are merged (if missing, all languages are merged)")
216     parser.add_option("-o", "--overwrite", action="store_true", dest="overwrite", default=False,
217                       help="overwrite existing target translations with source translations (if missing, only new translations are added)")
218     parser.add_option("-n", "--nonnull", action="store_true", dest="nonnull", default=False,
219                       help="do not update target translations with source translations that are identical to the untranslated text)")
220     (options, args) = parser.parse_args(argv)
221     if len(args) <= 1:
222         parser.print_help()
223         return 0
224
225     toolsdir = os.path.dirname(args[0])
226     if options.target:
227         podir1 = os.path.abspath(options.target)
228     else:
229         podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
230     podir2 = os.path.abspath(args[1])
231
232     if options.language:
233         name = options.language + '.po'
234         mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options)
235     else:
236         for i in os.listdir(podir1):
237             (base, ext) = os.path.splitext(i)
238             if ext != ".po":
239                 continue
240             mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options)
241
242     return 0
243
244
245 if __name__ == "__main__":
246     main(sys.argv)