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, options):
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():
80 if options.nonnull and other.msgstr == other.msgid:
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)
88 for i in range(0, len(new_lines)):
90 msgstr_lines.append(new_lines[i] + '"')
92 msgstr_lines.append('#~ "' + new_lines[i] + '"')
94 msgstr_lines.append('"' + new_lines[i] + '"')
98 def mergepo_polib(target, source, options):
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)
107 if options.nonnull and other.msgstr == other.msgid:
109 if other.translated() and other.msgstr != entry.msgstr:
110 entry.msgstr = other.msgstr
111 changed = changed + 1
113 for entry in po1.untranslated_entries():
114 other = po2.find(entry.msgid, include_obsolete_entries=True)
117 if options.nonnull and other.msgstr == other.msgid:
119 if other.translated():
120 entry.msgstr = other.msgstr
121 changed = changed + 1
127 def mergepo_minimaldiff(target, source, options):
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':
135 po1 = open(target, 'rb')
144 for line in oldlines:
146 if line.find('"') == 0 or line.find('#~ "') == 0:
147 msgid_lines.append(line)
150 msgid = parse_msg(msgid_lines)
151 newlines.extend(msgid_lines)
154 if line.find('"') == 0 or line.find('#~ "') == 0:
155 msgstr_lines.append(line)
158 changed = changed + translate(msgid, msgstr_lines, po2, options)
159 newlines.extend(msgstr_lines)
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)
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
170 msgstr_lines.append(line)
173 newlines.append(line)
175 # the file ended with a msgstr
176 changed = changed + translate(msgid, msgstr_lines, po2, options)
177 newlines.extend(msgstr_lines)
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')
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))
193 if not os.path.exists(target):
194 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
196 sys.stderr.write('Merging %s into %s: ' % (source, target))
198 changed = mergepo_minimaldiff(target, source, options)
199 sys.stderr.write('Updated %d translations with minimal diff.\n' % changed)
201 changed = mergepo_polib(target, source, options)
202 sys.stderr.write('Updated %d translations using polib.\n' % changed)
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)
225 toolsdir = os.path.dirname(args[0])
227 podir1 = os.path.abspath(options.target)
229 podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
230 podir2 = os.path.abspath(args[1])
233 name = options.language + '.po'
234 mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options)
236 for i in os.listdir(podir1):
237 (base, ext) = os.path.splitext(i)
240 mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options)
245 if __name__ == "__main__":