Mercurial > p > roundup > code
annotate roundup/cgi/TAL/talgettext.py @ 3854:f4e8dc583256
Restored subject parser regexp to the string it was before the...
...implementation of customization of it, i.e., the version from
CVS revision 1.184 of mailgw.py.
This makes 'testFollowupTitleMatchMultiRe' work again.
| author | Erik Forsberg <forsberg@users.sourceforge.net> |
|---|---|
| date | Sat, 12 May 2007 16:14:54 +0000 |
| parents | e816a232f988 |
| children | 6e3e4f24c753 |
| rev | line source |
|---|---|
| 2352 | 1 #!/usr/bin/env python |
| 2 ############################################################################## | |
| 3 # | |
| 4 # Copyright (c) 2002 Zope Corporation and Contributors. | |
| 5 # All Rights Reserved. | |
| 6 # | |
| 7 # This software is subject to the provisions of the Zope Public License, | |
| 8 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. | |
| 9 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |
| 10 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 11 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |
| 12 # FOR A PARTICULAR PURPOSE. | |
| 13 # | |
| 14 ############################################################################## | |
| 15 # Modifications for Roundup: | |
| 16 # 1. commented out ITALES references | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
17 # 2. escape quotes and line feeds in msgids |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
18 # 3. don't collect empty msgids |
| 2352 | 19 |
| 20 """Program to extract internationalization markup from Page Templates. | |
| 21 | |
| 22 Once you have marked up a Page Template file with i18n: namespace tags, use | |
| 23 this program to extract GNU gettext .po file entries. | |
| 24 | |
| 25 Usage: talgettext.py [options] files | |
| 26 Options: | |
| 27 -h / --help | |
| 28 Print this message and exit. | |
| 29 -o / --output <file> | |
| 30 Output the translation .po file to <file>. | |
| 31 -u / --update <file> | |
| 32 Update the existing translation <file> with any new translation strings | |
| 33 found. | |
| 34 """ | |
| 35 | |
| 36 import sys | |
| 37 import time | |
| 38 import getopt | |
| 39 import traceback | |
| 40 | |
| 41 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser | |
| 42 from roundup.cgi.TAL.TALInterpreter import TALInterpreter | |
| 43 from roundup.cgi.TAL.DummyEngine import DummyEngine | |
| 44 #from ITALES import ITALESEngine | |
| 45 from roundup.cgi.TAL.TALDefs import TALESError | |
| 46 | |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
47 __version__ = '$Revision: 1.6 $' |
| 2352 | 48 |
| 49 pot_header = '''\ | |
| 50 # SOME DESCRIPTIVE TITLE. | |
| 51 # Copyright (C) YEAR ORGANIZATION | |
| 52 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. | |
| 53 # | |
| 54 msgid "" | |
| 55 msgstr "" | |
| 56 "Project-Id-Version: PACKAGE VERSION\\n" | |
| 57 "POT-Creation-Date: %(time)s\\n" | |
| 58 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" | |
| 59 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" | |
| 60 "Language-Team: LANGUAGE <LL@li.org>\\n" | |
| 61 "MIME-Version: 1.0\\n" | |
| 62 "Content-Type: text/plain; charset=CHARSET\\n" | |
| 63 "Content-Transfer-Encoding: ENCODING\\n" | |
| 64 "Generated-By: talgettext.py %(version)s\\n" | |
| 65 ''' | |
| 66 | |
| 67 NLSTR = '"\n"' | |
| 68 | |
| 69 try: | |
| 70 True | |
| 71 except NameError: | |
| 72 True=1 | |
| 73 False=0 | |
| 74 | |
| 75 def usage(code, msg=''): | |
| 76 # Python 2.1 required | |
| 77 print >> sys.stderr, __doc__ | |
| 78 if msg: | |
| 79 print >> sys.stderr, msg | |
| 80 sys.exit(code) | |
| 81 | |
| 82 | |
| 83 class POTALInterpreter(TALInterpreter): | |
| 84 def translate(self, msgid, default, i18ndict=None, obj=None): | |
| 85 # XXX is this right? | |
| 86 if i18ndict is None: | |
| 87 i18ndict = {} | |
| 88 if obj: | |
| 89 i18ndict.update(obj) | |
| 90 # XXX Mmmh, it seems that sometimes the msgid is None; is that really | |
| 91 # possible? | |
| 92 if msgid is None: | |
| 93 return None | |
| 94 # XXX We need to pass in one of context or target_language | |
| 95 return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, | |
| 96 position=self.position, default=default) | |
| 97 | |
| 98 | |
| 99 class POEngine(DummyEngine): | |
| 100 #__implements__ = ITALESEngine | |
| 101 | |
| 102 def __init__(self, macros=None): | |
| 103 self.catalog = {} | |
| 104 DummyEngine.__init__(self, macros) | |
| 105 | |
| 106 def evaluate(*args): | |
| 107 return '' # who cares | |
| 108 | |
| 109 def evaluatePathOrVar(*args): | |
| 110 return '' # who cares | |
| 111 | |
| 112 def evaluateSequence(self, expr): | |
| 113 return (0,) # dummy | |
| 114 | |
| 115 def evaluateBoolean(self, expr): | |
| 116 return True # dummy | |
| 117 | |
| 118 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 119 # XXX position is not part of the ITALESEngine | |
| 120 # interface | |
| 121 position=None): | |
| 122 | |
|
2802
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
123 if not msgid: return 'x' |
|
e816a232f988
don't collect empty msgids
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2385
diff
changeset
|
124 |
| 2352 | 125 if domain not in self.catalog: |
| 126 self.catalog[domain] = {} | |
| 127 domain = self.catalog[domain] | |
| 128 | |
| 129 if msgid not in domain: | |
| 130 domain[msgid] = [] | |
| 131 domain[msgid].append((self.file, position)) | |
| 132 return 'x' | |
| 133 | |
| 134 | |
| 135 class UpdatePOEngine(POEngine): | |
| 136 """A slightly-less braindead POEngine which supports loading an existing | |
| 137 .po file first.""" | |
| 138 | |
| 139 def __init__ (self, macros=None, filename=None): | |
| 140 POEngine.__init__(self, macros) | |
| 141 | |
| 142 self._filename = filename | |
| 143 self._loadFile() | |
| 144 self.base = self.catalog | |
| 145 self.catalog = {} | |
| 146 | |
| 147 def __add(self, id, s, fuzzy): | |
| 148 "Add a non-fuzzy translation to the dictionary." | |
| 149 if not fuzzy and str: | |
| 150 # check for multi-line values and munge them appropriately | |
| 151 if '\n' in s: | |
| 152 lines = s.rstrip().split('\n') | |
| 153 s = NLSTR.join(lines) | |
| 154 self.catalog[id] = s | |
| 155 | |
| 156 def _loadFile(self): | |
| 157 # shamelessly cribbed from Python's Tools/i18n/msgfmt.py | |
| 158 # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) | |
| 159 # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) | |
| 160 | |
| 161 ID = 1 | |
| 162 STR = 2 | |
| 163 | |
| 164 try: | |
| 165 lines = open(self._filename).readlines() | |
| 166 except IOError, msg: | |
| 167 print >> sys.stderr, msg | |
| 168 sys.exit(1) | |
| 169 | |
| 170 section = None | |
| 171 fuzzy = False | |
| 172 | |
| 173 # Parse the catalog | |
| 174 lno = 0 | |
| 175 for l in lines: | |
| 176 lno += True | |
| 177 # If we get a comment line after a msgstr, this is a new entry | |
| 178 if l[0] == '#' and section == STR: | |
| 179 self.__add(msgid, msgstr, fuzzy) | |
| 180 section = None | |
| 181 fuzzy = False | |
| 182 # Record a fuzzy mark | |
| 183 if l[:2] == '#,' and l.find('fuzzy'): | |
| 184 fuzzy = True | |
| 185 # Skip comments | |
| 186 if l[0] == '#': | |
| 187 continue | |
| 188 # Now we are in a msgid section, output previous section | |
| 189 if l.startswith('msgid'): | |
| 190 if section == STR: | |
| 191 self.__add(msgid, msgstr, fuzzy) | |
| 192 section = ID | |
| 193 l = l[5:] | |
| 194 msgid = msgstr = '' | |
| 195 # Now we are in a msgstr section | |
| 196 elif l.startswith('msgstr'): | |
| 197 section = STR | |
| 198 l = l[6:] | |
| 199 # Skip empty lines | |
| 200 if not l.strip(): | |
| 201 continue | |
| 202 # XXX: Does this always follow Python escape semantics? | |
| 203 l = eval(l) | |
| 204 if section == ID: | |
| 205 msgid += l | |
| 206 elif section == STR: | |
| 207 msgstr += '%s\n' % l | |
| 208 else: | |
| 209 print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ | |
| 210 'before:' | |
| 211 print >> sys.stderr, l | |
| 212 sys.exit(1) | |
| 213 # Add last entry | |
| 214 if section == STR: | |
| 215 self.__add(msgid, msgstr, fuzzy) | |
| 216 | |
| 217 def evaluate(self, expression): | |
| 218 try: | |
| 219 return POEngine.evaluate(self, expression) | |
| 220 except TALESError: | |
| 221 pass | |
| 222 | |
| 223 def evaluatePathOrVar(self, expr): | |
| 224 return 'who cares' | |
| 225 | |
| 226 def translate(self, msgid, domain=None, mapping=None, default=None, | |
| 227 position=None): | |
| 228 if msgid not in self.base: | |
| 229 POEngine.translate(self, msgid, domain, mapping, default, position) | |
| 230 return 'x' | |
| 231 | |
| 232 | |
| 233 def main(): | |
| 234 try: | |
| 235 opts, args = getopt.getopt( | |
| 236 sys.argv[1:], | |
| 237 'ho:u:', | |
| 238 ['help', 'output=', 'update=']) | |
| 239 except getopt.error, msg: | |
| 240 usage(1, msg) | |
| 241 | |
| 242 outfile = None | |
| 243 engine = None | |
| 244 update_mode = False | |
| 245 for opt, arg in opts: | |
| 246 if opt in ('-h', '--help'): | |
| 247 usage(0) | |
| 248 elif opt in ('-o', '--output'): | |
| 249 outfile = arg | |
| 250 elif opt in ('-u', '--update'): | |
| 251 update_mode = True | |
| 252 if outfile is None: | |
| 253 outfile = arg | |
| 254 engine = UpdatePOEngine(filename=arg) | |
| 255 | |
| 256 if not args: | |
| 257 print 'nothing to do' | |
| 258 return | |
| 259 | |
| 260 # We don't care about the rendered output of the .pt file | |
| 261 class Devnull: | |
| 262 def write(self, s): | |
| 263 pass | |
| 264 | |
| 265 # check if we've already instantiated an engine; | |
| 266 # if not, use the stupidest one available | |
| 267 if not engine: | |
| 268 engine = POEngine() | |
| 269 | |
| 270 # process each file specified | |
| 271 for filename in args: | |
| 272 try: | |
| 273 engine.file = filename | |
| 274 p = HTMLTALParser() | |
| 275 p.parseFile(filename) | |
| 276 program, macros = p.getCode() | |
| 277 POTALInterpreter(program, macros, engine, stream=Devnull(), | |
| 278 metal=False)() | |
| 279 except: # Hee hee, I love bare excepts! | |
| 280 print 'There was an error processing', filename | |
| 281 traceback.print_exc() | |
| 282 | |
| 283 # Now output the keys in the engine. Write them to a file if --output or | |
| 284 # --update was specified; otherwise use standard out. | |
| 285 if (outfile is None): | |
| 286 outfile = sys.stdout | |
| 287 else: | |
| 288 outfile = file(outfile, update_mode and "a" or "w") | |
| 289 | |
| 290 catalog = {} | |
| 291 for domain in engine.catalog.keys(): | |
| 292 catalog.update(engine.catalog[domain]) | |
| 293 | |
| 294 messages = catalog.copy() | |
| 295 try: | |
| 296 messages.update(engine.base) | |
| 297 except AttributeError: | |
| 298 pass | |
| 299 if '' not in messages: | |
| 300 print >> outfile, pot_header % {'time': time.ctime(), | |
| 301 'version': __version__} | |
| 302 | |
| 303 msgids = catalog.keys() | |
| 304 # XXX: You should not sort by msgid, but by filename and position. (SR) | |
| 305 msgids.sort() | |
| 306 for msgid in msgids: | |
| 307 positions = catalog[msgid] | |
| 308 for filename, position in positions: | |
| 309 outfile.write('#: %s:%s\n' % (filename, position[0])) | |
| 310 | |
|
2385
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
311 outfile.write('msgid "%s"\n' |
|
85cbfc4a5946
escape newlines
Alexander Smishlajev <a1s@users.sourceforge.net>
parents:
2382
diff
changeset
|
312 % msgid.replace('"', '\\"').replace("\n", '\\n"\n"')) |
| 2352 | 313 outfile.write('msgstr ""\n') |
| 314 outfile.write('\n') | |
| 315 | |
| 316 | |
| 317 if __name__ == '__main__': | |
| 318 main() |
