]> git.lyx.org Git - lyx.git/blob - development/tools/mergepo.py
Update translation of Listings by Jari-Matti Mäkelä
[lyx.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 io
18 import polib
19 from optparse import OptionParser
20
21
22 def trim_eol(line):
23     " Remove end of line char."
24     if line[-1:] == '\n':
25         return line[:-1]
26     else:
27         # file with no EOL in last line
28         return line
29
30
31 def read(input):
32     " Read utf8 input file and strip lineendings."
33     lines = list()
34     while 1:
35         line = input.readline()
36         if not line:
37             break
38         line = trim_eol(line)
39         lines.append(line)
40     return lines
41
42
43 def parse_msg(lines):
44     " Extracts msgid or msgstr from lines."
45     if len(lines) < 1:
46         return ''
47     i = lines[0].find('"')
48     if i < 0:
49         return ''
50     msg = lines[0][i:].strip('"')
51     for i in range(1, len(lines)):
52         j = lines[i].find('"')
53         if j < 0:
54             return ''
55         msg = msg + lines[i][j:].strip('"')
56     return polib.unescape(msg)
57
58
59 def translate(msgid, flags, msgstr_lines, po2, options):
60     msgstr = parse_msg(msgstr_lines)
61     if options.overwrite:
62         other = po2.find(msgid)
63         if not other:
64             return 0
65         if not other.translated():
66             return 0
67         if msgstr == other.msgstr:
68             return 0
69     else:
70         if msgstr != '' and not u'fuzzy' in flags:
71             return 0
72         other = po2.find(msgid)
73         if not other:
74             return 0
75         if not other.translated():
76             return 0
77     if options.nonnull and other.msgstr == other.msgid:
78         return 0
79     msgstr = other.msgstr
80     if 'fuzzy' in other.flags:
81         if not u'fuzzy' in flags:
82             flags.append(u'fuzzy')
83     else:
84         if u'fuzzy' in flags:
85             flags.remove(u'fuzzy')
86     obsolete = (msgstr_lines[0].find('#~') == 0)
87     j = msgstr_lines[0].find('"')
88     # must not assign to msgstr_lines, because that would not be seen by our caller
89     new_lines = polib.wrap(msgstr_lines[0][0:j+1] + polib.escape(msgstr), 76, drop_whitespace = False)
90     del msgstr_lines[:]
91     for i in range(0, len(new_lines)):
92         if i == 0:
93             msgstr_lines.append(new_lines[i] + '"')
94         elif obsolete:
95             msgstr_lines.append('#~ "' + new_lines[i] + '"')
96         else:
97             msgstr_lines.append('"' + new_lines[i] + '"')
98     return 1
99
100
101 def mergepo_polib(target, source, options):
102     changed = 0
103     po1 = polib.pofile(target)
104     po2 = polib.pofile(source)
105     if options.overwrite:
106         for entry in po1.entries():
107             other = po2.find(entry.msgid, include_obsolete_entries=True)
108             if not other:
109                 continue
110             if options.nonnull and other.msgstr == other.msgid:
111                 continue
112             if other.translated() and other.msgstr != entry.msgstr:
113                 entry.msgstr = other.msgstr
114                 if 'fuzzy' in other.flags:
115                     if not 'fuzzy' in entry.flags:
116                         entry.flags.append('fuzzy')
117                 else:
118                     if 'fuzzy' in entry.flags:
119                         entry.flags.remove('fuzzy')
120                 changed = changed + 1
121     else:
122         for entry in po1.untranslated_entries():
123             other = po2.find(entry.msgid, include_obsolete_entries=True)
124             if not other:
125                 continue
126             if options.nonnull and other.msgstr == other.msgid:
127                 continue
128             if other.translated():
129                 entry.msgstr = other.msgstr
130                 changed = changed + 1
131     if changed > 0:
132         po1.save(target)
133     return changed
134
135
136 def mergepo_minimaldiff(target, source, options):
137     changed = 0
138     po2 = polib.pofile(source)
139     target_enc = polib.detect_encoding(target)
140     # for utf8 files we can use our self written parser to minimize diffs,
141     # otherwise we need to use polib
142     if not target_enc in ['UTF-8', 'utf-8', 'utf_8']:
143         raise
144     # open file with universal newlines, since it can happen that we are
145     # on unix, but the file has been written on windows or vice versa.
146     po1 = io.open(target, 'r', encoding='utf_8', newline=None)
147     oldlines = read(po1)
148     po1.close()
149     newlines = []
150     in_msgid = False
151     in_msgstr = False
152     flags = []
153     msgstr_lines = []
154     msgid_lines = []
155     msgid = ''
156     for line in oldlines:
157         if in_msgid:
158             if line.find('"') == 0 or line.find('#~ "') == 0:
159                 msgid_lines.append(line)
160             else:
161                 in_msgid = False
162                 msgid = parse_msg(msgid_lines)
163         elif in_msgstr:
164             if line.find('"') == 0 or line.find('#~ "') == 0:
165                 msgstr_lines.append(line)
166             else:
167                 in_msgstr = False
168                 changed = changed + translate(msgid, flags, msgstr_lines, po2, options)
169                 if len(flags) > 0:
170                     flagline = u'#, ' + u', '.join(flags)
171                     newlines.append(flagline)
172                     flags = []
173                 newlines.extend(msgid_lines)
174                 newlines.extend(msgstr_lines)
175                 msgid_lines = []
176                 msgstr_lines = []
177                 msgid = ''
178         if not in_msgid and not in_msgstr:
179             if line.find('#,') == 0 and len(flags) == 0:
180                 flags = line[2:].strip().split(u', ')
181             elif line.find('msgid') == 0 or line.find('#~ msgid') == 0:
182                 msgid_lines.append(line)
183                 in_msgid = True
184             elif line.find('msgstr') == 0 or line.find('#~ msgstr') == 0:
185                 if line.find('msgstr[') == 0 or line.find('#~ msgstr[') == 0:
186                     # plural forms are not implemented
187                     raise
188                 msgstr_lines.append(line)
189                 in_msgstr = True
190             else:
191                 newlines.append(line)
192     if msgid != '':
193         # the file ended with a msgstr
194         changed = changed + translate(msgid, flags, msgstr_lines, po2, options)
195         if len(flags) > 0:
196             flagline = u'#, ' + u', '.join(flags)
197             newlines.append(flagline)
198             flags = []
199         newlines.extend(msgid_lines)
200         newlines.extend(msgstr_lines)
201         msgid_lines = []
202         msgstr_lines = []
203         msgid = ''
204     if changed > 0:
205         # we store .po files with unix line ends in git,
206         # so do always write them even on windows
207         po1 = io.open(target, 'w', encoding='utf_8', newline='\n')
208         for line in newlines:
209             po1.write(line + '\n')
210     return changed
211
212
213 def mergepo(target, source, options):
214     if not os.path.exists(source):
215         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, source))
216         return
217     if not os.path.exists(target):
218         sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
219         return
220     sys.stderr.write('Merging %s into %s: ' % (source, target))
221     try:
222         changed = mergepo_minimaldiff(target, source, options)
223         sys.stderr.write('Updated %d translations with minimal diff.\n' % changed)
224     except Exception as e:
225         sys.stderr.write('Unable to use minimal diff: %s\n' % e)
226         changed = mergepo_polib(target, source, options)
227         sys.stderr.write('Updated %d translations using polib.\n' % changed)
228
229
230 def main(argv):
231
232     parser = OptionParser(description = """This script reads translations from .po files in the given source directory
233 and adds all translations that do not already exist to the corresponding .po
234 files in the target directory. It is recommended to remerge strings from the
235 source code before running this script. Otherwise translations that are not
236 yet in the target .po files are not updated.""", usage = "Usage: %prog [options] sourcedir")
237     parser.add_option("-t", "--target", dest="target",
238                       help="target directory containing .po files. If missing, it is determined from the script location.")
239     parser.add_option("-l", "--language", dest="language",
240                       help="language for which translations are merged (if missing, all languages are merged)")
241     parser.add_option("-o", "--overwrite", action="store_true", dest="overwrite", default=False,
242                       help="overwrite existing target translations with source translations (if missing, only new translations are added)")
243     parser.add_option("-n", "--nonnull", action="store_true", dest="nonnull", default=False,
244                       help="do not update target translations with source translations that are identical to the untranslated text)")
245     (options, args) = parser.parse_args(argv)
246     if len(args) <= 1:
247         parser.print_help()
248         return 0
249
250     toolsdir = os.path.dirname(args[0])
251     if options.target:
252         podir1 = os.path.abspath(options.target)
253     else:
254         podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
255     podir2 = os.path.abspath(args[1])
256
257     if options.language:
258         name = options.language + '.po'
259         mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options)
260     else:
261         for i in os.listdir(podir1):
262             (base, ext) = os.path.splitext(i)
263             if ext != ".po":
264                 continue
265             mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options)
266
267     return 0
268
269
270 if __name__ == "__main__":
271     main(sys.argv)