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
19 from optparse import OptionParser
23 " Remove end of line char."
27 # file with no EOL in last line
32 " Read utf8 input file and strip lineendings."
35 line = input.readline()
44 " Extracts msgid or msgstr from lines."
47 i = lines[0].find('"')
50 msg = lines[0][i:].strip('"')
51 for i in range(1, len(lines)):
52 j = lines[i].find('"')
55 msg = msg + lines[i][j:].strip('"')
56 return polib.unescape(msg)
59 def translate(msgid, msgstr_lines, po2, options):
60 msgstr = parse_msg(msgstr_lines)
62 other = po2.find(msgid)
65 if not other.translated():
67 if msgstr == other.msgstr:
72 other = po2.find(msgid)
75 if not other.translated():
77 if options.nonnull and other.msgstr == other.msgid:
80 obsolete = (msgstr_lines[0].find('#~') == 0)
81 j = msgstr_lines[0].find('"')
82 # must not assign to msgstr_lines, because that would not be seen by our caller
83 new_lines = polib.wrap(msgstr_lines[0][0:j+1] + polib.escape(msgstr), 76, drop_whitespace = False)
85 for i in range(0, len(new_lines)):
87 msgstr_lines.append(new_lines[i] + '"')
89 msgstr_lines.append('#~ "' + new_lines[i] + '"')
91 msgstr_lines.append('"' + new_lines[i] + '"')
95 def mergepo_polib(target, source, options):
97 po1 = polib.pofile(target)
98 po2 = polib.pofile(source)
100 for entry in po1.entries():
101 other = po2.find(entry.msgid, include_obsolete_entries=True)
104 if options.nonnull and other.msgstr == other.msgid:
106 if other.translated() and other.msgstr != entry.msgstr:
107 entry.msgstr = other.msgstr
108 changed = changed + 1
110 for entry in po1.untranslated_entries():
111 other = po2.find(entry.msgid, include_obsolete_entries=True)
114 if options.nonnull and other.msgstr == other.msgid:
116 if other.translated():
117 entry.msgstr = other.msgstr
118 changed = changed + 1
124 def mergepo_minimaldiff(target, source, options):
126 po2 = polib.pofile(source)
127 target_enc = polib.detect_encoding(target)
128 # for utf8 files we can use our self written parser to minimize diffs,
129 # otherwise we need to use polib
130 if not target_enc in ['UTF-8', 'utf-8', 'utf_8']:
132 # open file with universal newlines, since it can happen that we are
133 # on unix, but the file has been written on windows or vice versa.
134 po1 = io.open(target, 'r', encoding='utf_8', newline=None)
143 for line in oldlines:
145 if line.find('"') == 0 or line.find('#~ "') == 0:
146 msgid_lines.append(line)
149 msgid = parse_msg(msgid_lines)
150 newlines.extend(msgid_lines)
153 if line.find('"') == 0 or line.find('#~ "') == 0:
154 msgstr_lines.append(line)
157 changed = changed + translate(msgid, msgstr_lines, po2, options)
158 newlines.extend(msgstr_lines)
161 if not in_msgid and not in_msgstr:
162 if line.find('msgid') == 0 or line.find('#~ msgid') == 0:
163 msgid_lines.append(line)
165 elif line.find('msgstr') == 0 or line.find('#~ msgstr') == 0:
166 if line.find('msgstr[') == 0 or line.find('#~ msgstr[') == 0:
167 # plural forms are not implemented
169 msgstr_lines.append(line)
172 newlines.append(line)
174 # the file ended with a msgstr
175 changed = changed + translate(msgid, msgstr_lines, po2, options)
176 newlines.extend(msgstr_lines)
180 # we store .po files with unix line ends in git,
181 # so do always write them even on windows
182 po1 = io.open(target, 'w', encoding='utf_8', newline='\n')
183 for line in newlines:
184 po1.write(line + '\n')
188 def mergepo(target, source, options):
189 if not os.path.exists(source):
190 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, source))
192 if not os.path.exists(target):
193 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
195 sys.stderr.write('Merging %s into %s: ' % (source, target))
197 changed = mergepo_minimaldiff(target, source, options)
198 sys.stderr.write('Updated %d translations with minimal diff.\n' % changed)
200 changed = mergepo_polib(target, source, options)
201 sys.stderr.write('Updated %d translations using polib.\n' % changed)
206 parser = OptionParser(description = """This script reads translations from .po files in the given source directory
207 and adds all translations that do not already exist to the corresponding .po
208 files in the target directory. It is recommended to remerge strings from the
209 source code before running this script. Otherwise translations that are not
210 yet in the target .po files are not updated.""", usage = "Usage: %prog [options] sourcedir")
211 parser.add_option("-t", "--target", dest="target",
212 help="target directory containing .po files. If missing, it is determined from the script location.")
213 parser.add_option("-l", "--language", dest="language",
214 help="language for which translations are merged (if missing, all languages are merged)")
215 parser.add_option("-o", "--overwrite", action="store_true", dest="overwrite", default=False,
216 help="overwrite existing target translations with source translations (if missing, only new translations are added)")
217 parser.add_option("-n", "--nonnull", action="store_true", dest="nonnull", default=False,
218 help="do not update target translations with source translations that are identical to the untranslated text)")
219 (options, args) = parser.parse_args(argv)
224 toolsdir = os.path.dirname(args[0])
226 podir1 = os.path.abspath(options.target)
228 podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
229 podir2 = os.path.abspath(args[1])
232 name = options.language + '.po'
233 mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options)
235 for i in os.listdir(podir1):
236 (base, ext) = os.path.splitext(i)
239 mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options)
244 if __name__ == "__main__":