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