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, flags, 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 if 'fuzzy' in other.flags:
81 if not u'fuzzy' in flags:
82 flags.append(u'fuzzy')
85 flags.remove(u'fuzzy')
86 obsolete = (msgstr_lines[0].find('#~') == 0)
87 j = msgstr_lines[0].find('"')
88 # must not assign to msgstr_lines, because that would not be seen by our caller
89 new_lines = polib.wrap(msgstr_lines[0][0:j+1] + polib.escape(msgstr), 76, drop_whitespace = False)
91 for i in range(0, len(new_lines)):
93 msgstr_lines.append(new_lines[i] + '"')
95 msgstr_lines.append('#~ "' + new_lines[i] + '"')
97 msgstr_lines.append('"' + new_lines[i] + '"')
101 def mergepo_polib(target, source, options):
103 po1 = polib.pofile(target)
104 po2 = polib.pofile(source)
105 if options.overwrite:
106 for entry in po1.entries():
107 other = po2.find(entry.msgid, include_obsolete_entries=True)
110 if options.nonnull and other.msgstr == other.msgid:
112 if other.translated() and other.msgstr != entry.msgstr:
113 entry.msgstr = other.msgstr
114 if 'fuzzy' in other.flags:
115 if not 'fuzzy' in entry.flags:
116 entry.flags.append('fuzzy')
118 if 'fuzzy' in entry.flags:
119 entry.flags.remove('fuzzy')
120 changed = changed + 1
122 for entry in po1.untranslated_entries():
123 other = po2.find(entry.msgid, include_obsolete_entries=True)
126 if options.nonnull and other.msgstr == other.msgid:
128 if other.translated():
129 entry.msgstr = other.msgstr
130 changed = changed + 1
136 def mergepo_minimaldiff(target, source, options):
138 po2 = polib.pofile(source)
139 target_enc = polib.detect_encoding(target)
140 # for utf8 files we can use our self written parser to minimize diffs,
141 # otherwise we need to use polib
142 if not target_enc in ['UTF-8', 'utf-8', 'utf_8']:
144 # open file with universal newlines, since it can happen that we are
145 # on unix, but the file has been written on windows or vice versa.
146 po1 = io.open(target, 'r', encoding='utf_8', newline=None)
156 for line in oldlines:
158 if line.find('"') == 0 or line.find('#~ "') == 0:
159 msgid_lines.append(line)
162 msgid = parse_msg(msgid_lines)
164 if line.find('"') == 0 or line.find('#~ "') == 0:
165 msgstr_lines.append(line)
168 changed = changed + translate(msgid, flags, msgstr_lines, po2, options)
170 flagline = u'#, ' + u', '.join(flags)
171 newlines.append(flagline)
173 newlines.extend(msgid_lines)
174 newlines.extend(msgstr_lines)
178 if not in_msgid and not in_msgstr:
179 if line.find('#,') == 0 and len(flags) == 0:
180 flags = line[2:].strip().split(u', ')
181 elif line.find('msgid') == 0 or line.find('#~ msgid') == 0:
182 msgid_lines.append(line)
184 elif line.find('msgstr') == 0 or line.find('#~ msgstr') == 0:
185 if line.find('msgstr[') == 0 or line.find('#~ msgstr[') == 0:
186 # plural forms are not implemented
188 msgstr_lines.append(line)
191 newlines.append(line)
193 # the file ended with a msgstr
194 changed = changed + translate(msgid, flags, msgstr_lines, po2, options)
196 flagline = u'#, ' + u', '.join(flags)
197 newlines.append(flagline)
199 newlines.extend(msgid_lines)
200 newlines.extend(msgstr_lines)
205 # we store .po files with unix line ends in git,
206 # so do always write them even on windows
207 po1 = io.open(target, 'w', encoding='utf_8', newline='\n')
208 for line in newlines:
209 po1.write(line + '\n')
213 def mergepo(target, source, options):
214 if not os.path.exists(source):
215 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, source))
217 if not os.path.exists(target):
218 sys.stderr.write('Skipping %s since %s does not exist.\n' % (target, target))
220 sys.stderr.write('Merging %s into %s: ' % (source, target))
222 changed = mergepo_minimaldiff(target, source, options)
223 sys.stderr.write('Updated %d translations with minimal diff.\n' % changed)
224 except Exception as e:
225 sys.stderr.write('Unable to use minimal diff: %s\n' % e)
226 changed = mergepo_polib(target, source, options)
227 sys.stderr.write('Updated %d translations using polib.\n' % changed)
232 parser = OptionParser(description = """This script reads translations from .po files in the given source directory
233 and adds all translations that do not already exist to the corresponding .po
234 files in the target directory. It is recommended to remerge strings from the
235 source code before running this script. Otherwise translations that are not
236 yet in the target .po files are not updated.""", usage = "Usage: %prog [options] sourcedir")
237 parser.add_option("-t", "--target", dest="target",
238 help="target directory containing .po files. If missing, it is determined from the script location.")
239 parser.add_option("-l", "--language", dest="language",
240 help="language for which translations are merged (if missing, all languages are merged)")
241 parser.add_option("-o", "--overwrite", action="store_true", dest="overwrite", default=False,
242 help="overwrite existing target translations with source translations (if missing, only new translations are added)")
243 parser.add_option("-n", "--nonnull", action="store_true", dest="nonnull", default=False,
244 help="do not update target translations with source translations that are identical to the untranslated text)")
245 (options, args) = parser.parse_args(argv)
250 toolsdir = os.path.dirname(args[0])
252 podir1 = os.path.abspath(options.target)
254 podir1 = os.path.normpath(os.path.join(toolsdir, '../../po'))
255 podir2 = os.path.abspath(args[1])
258 name = options.language + '.po'
259 mergepo(os.path.join(podir1, name), os.path.join(podir2, name), options)
261 for i in os.listdir(podir1):
262 (base, ext) = os.path.splitext(i)
265 mergepo(os.path.join(podir1, i), os.path.join(podir2, i), options)
270 if __name__ == "__main__":