2 # -*- coding: utf-8 -*-
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
10 # Full author contact details are available in file CREDITS
12 # This script takes missing translations from another set of po files and
13 # merges them into the po files in this source tree.
16 import os, re, string, sys
18 from optparse import OptionParser
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.
24 " Remove end of line char(s)."
25 if line[-2:-1] == '\r':
27 elif line[-1:] == '\r' or line[-1:] == '\n':
30 # file with no EOL in last line
35 " Read utf8 input file and strip lineendings."
38 line = input.readline()
42 lines.append(line.decode('UTF-8'))
47 " Extracts msgid or msgstr from lines."
50 i = lines[0].find('"')
53 msg = lines[0][i:].strip('"')
54 for i in range(1, len(lines)):
55 j = lines[i].find('"')
58 msg = msg + lines[i][j:].strip('"')
59 return polib.unescape(msg)
62 def translate(msgid, msgstr_lines, po2, overwrite):
63 msgstr = parse_msg(msgstr_lines)
65 other = po2.find(msgid)
68 if not other.translated():
70 if msgstr == other.msgstr:
75 other = po2.find(msgid)
78 if not other.translated():
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)
86 for i in range(0, len(new_lines)):
88 msgstr_lines.append(new_lines[i] + '"')
90 msgstr_lines.append('#~ "' + new_lines[i] + '"')
92 msgstr_lines.append('"' + new_lines[i] + '"')
96 def mergepo_polib(target, source, overwrite):
98 po1 = polib.pofile(target)
99 po2 = polib.pofile(source)
101 for entry in po1.entries():
102 other = po2.find(entry.msgid, include_obsolete_entries=True)
105 if other.translated() and other.msgstr != entry.msgstr:
106 entry.msgstr = other.msgstr
107 changed = changed + 1
109 for entry in po1.untranslated_entries():
110 other = po2.find(entry.msgid, include_obsolete_entries=True)
113 if other.translated():
114 entry.msgstr = other.msgstr
115 changed = changed + 1
121 def mergepo_minimaldiff(target, source, overwrite):
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':
129 po1 = open(target, 'rb')
138 for line in oldlines:
140 if line.find('"') == 0 or line.find('#~ "') == 0:
141 msgid_lines.append(line)
144 msgid = parse_msg(msgid_lines)
145 newlines.extend(msgid_lines)
148 if line.find('"') == 0 or line.find('#~ "') == 0:
149 msgstr_lines.append(line)
152 changed = changed + translate(msgid, msgstr_lines, po2, overwrite)
153 newlines.extend(msgstr_lines)
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)
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
164 msgstr_lines.append(line)
167 newlines.append(line)
169 # the file ended with a msgstr
170 changed = changed + translate(msgid, msgstr_lines, po2, overwrite)
171 newlines.extend(msgstr_lines)
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')
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))
187 if not os.path.exists(target):
188 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
190 sys.stderr.write('Merging %s into %s: ' % (source, target))
192 changed = mergepo_minimaldiff(target, source, overwrite)
194 changed = mergepo_polib(target, source, overwrite)
195 sys.stderr.write('Updated %d translations.\n' % changed)
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)
216 toolsdir = os.path.dirname(args[0])
218 podir1 = os.path.abspath(options.target)
220 podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
221 podir2 = os.path.abspath(args[1])
224 name = options.language + '.po'
225 mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options.overwrite)
227 for i in os.listdir(podir1):
228 (base, ext) = os.path.splitext(i)
231 mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options.overwrite)
236 if __name__ == "__main__":