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