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