1#!/usr/bin/env python
2#
3#
4# Licensed to the Apache Software Foundation (ASF) under one
5# or more contributor license agreements.  See the NOTICE file
6# distributed with this work for additional information
7# regarding copyright ownership.  The ASF licenses this file
8# to you under the Apache License, Version 2.0 (the
9# "License"); you may not use this file except in compliance
10# with the License.  You may obtain a copy of the License at
11#
12#   http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing,
15# software distributed under the License is distributed on an
16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17# KIND, either express or implied.  See the License for the
18# specific language governing permissions and limitations
19# under the License.
20#
21#
22
23import os, re, sys
24
25msgstr_re = re.compile('msgstr\[\d+\] "')
26
27def parse_translation(f):
28    """Read a single translation entry from the file F and return a
29    tuple with the comments, msgid, msgid_plural and msgstr.  The comments is
30    returned as a list of lines which do not end in new-lines.  The msgid is
31    string.  The msgid_plural is string or None.  The msgstr is a list of
32    strings.  The msgid, msgid_plural and msgstr strings can contain embedded
33    newlines"""
34    line = f.readline()
35
36    # Parse comments
37    comments = []
38    while True:
39        if line.strip() == '' or line[:2] == '#~':
40            return comments, None, None, None
41        elif line[0] == '#':
42            comments.append(line[:-1])
43        else:
44            break
45        line = f.readline()
46
47    # Parse msgid
48    if line[:7] != 'msgid "' or line[-2] != '"':
49        raise RuntimeError("parse error")
50    msgid = line[6:-1]
51    while True:
52        line = f.readline()
53        if line[0] != '"':
54            break
55        msgid = msgid[:-1] + line[1:-1]
56
57    # Parse optional msgid_plural
58    msgid_plural = None
59    if line[:14] == 'msgid_plural "':
60        if line[-2] != '"':
61            raise RuntimeError("parse error")
62        msgid_plural = line[13:-1]
63        while True:
64            line = f.readline()
65            if line[0] != '"':
66                break
67            msgid_plural = msgid_plural[:-1] + line[1:-1]
68
69    # Parse msgstr
70    msgstr = []
71    if not msgid_plural:
72        if line[:8] != 'msgstr "' or line[-2] != '"':
73            raise RuntimeError("parse error")
74        msgstr.append(line[7:-1])
75        while True:
76            line = f.readline()
77            if len(line) == 0 or line[0] != '"':
78                break
79            msgstr[0] += '\n' + line[:-1]
80    else:
81        if line[:7] != 'msgstr[' or line[-2] != '"':
82            raise RuntimeError("parse error")
83        i = 0
84        while True:
85            matched_msgstr = msgstr_re.match(line)
86            if matched_msgstr:
87                matched_msgstr_len = len(matched_msgstr.group(0))
88                msgstr.append(line[matched_msgstr_len-1:-1])
89            else:
90                break
91            while True:
92                line = f.readline()
93                if len(line) == 0 or line[0] != '"':
94                    break
95                msgstr[i] += '\n' + line[:-1]
96            i += 1
97
98    if line.strip() != '':
99        raise RuntimeError("parse error")
100
101    return comments, msgid, msgid_plural, msgstr
102
103def split_comments(comments):
104    """Split COMMENTS into flag comments and other comments.  Flag
105    comments are those that begin with '#,', e.g. '#,fuzzy'."""
106    flags = []
107    other = []
108    for c in comments:
109        if len(c) > 1 and c[1] == ',':
110            flags.append(c)
111        else:
112            other.append(c)
113    return flags, other
114
115def main(argv):
116    if len(argv) != 2:
117        argv0 = os.path.basename(argv[0])
118        sys.exit('Usage: %s <lang.po>\n'
119                 '\n'
120                 'This script will replace the translations and flags in lang.po (LF line endings)\n'
121                 'with the translations and flags in the source po file read from standard input.\n'
122                 'Strings that are not found in the source file are left untouched.\n'
123                 'A backup copy of lang.po is saved as lang.po.bak.\n'
124                 '\n'
125                 'Example:\n'
126                 '    svn cat http://svn.apache.org/repos/asf/subversion/trunk/subversion/po/sv.po | \\\n'
127                 '        %s sv.po' % (argv0, argv0))
128
129    # Read the source po file into a hash
130    source = {}
131    while True:
132        comments, msgid, msgid_plural, msgstr = parse_translation(sys.stdin)
133        if not comments and msgid is None:
134            break
135        if msgid is not None:
136            source[msgid] = msgstr, split_comments(comments)[0]
137
138    # Make a backup of the output file, open the copy for reading
139    # and the original for writing.
140    os.rename(argv[1], argv[1] + '.bak')
141    infile = open(argv[1] + '.bak')
142    outfile = open(argv[1], 'w')
143
144    # Loop thought the original and replace stuff as we go
145    first = 1
146    string_count = 0
147    update_count = 0
148    untranslated = 0
149    fuzzy = 0
150    while True:
151        comments, msgid, msgid_plural, msgstr = parse_translation(infile)
152        if not comments and msgid is None:
153            break
154        if not first:
155            outfile.write('\n')
156        first = 0
157        if msgid is None:
158            outfile.write('\n'.join(comments) + '\n')
159        else:
160            string_count += 1
161            # Do not update the header, and only update if the source
162            # has a non-empty translation.
163            if msgid != '""' and source.get(msgid, ['""', []])[0] != '""':
164                other = split_comments(comments)[1]
165                new_msgstr, new_flags = source[msgid]
166                new_comments = other + new_flags
167                if new_msgstr != msgstr or new_comments != comments:
168                    update_count += 1
169                    msgstr = new_msgstr
170                    comments = new_comments
171            outfile.write('\n'.join(comments) + '\n')
172            outfile.write('msgid ' + msgid + '\n')
173            if not msgid_plural:
174                outfile.write('msgstr ' + msgstr[0] + '\n')
175            else:
176                outfile.write('msgid_plural ' + msgid_plural + '\n')
177                n = 0
178                for i in msgstr:
179                    outfile.write('msgstr[%s] %s\n' % (n, msgstr[n]))
180                    n += 1
181        if msgstr is not None:
182            for m in msgstr:
183                if m == '""':
184                    untranslated += 1
185        for c in comments:
186            if c.startswith('#,') and 'fuzzy' in c.split(', '):
187                fuzzy += 1
188
189    # We're done.  Tell the user what we did.
190    print(('%d strings updated. '
191          '%d fuzzy strings. '
192          '%d of %d strings are still untranslated (%.0f%%).' %
193          (update_count, fuzzy, untranslated, string_count,
194           100.0 * untranslated / string_count)))
195
196if __name__ == '__main__':
197    main(sys.argv)
198