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