Mercurial > p > roundup > code
annotate roundup/cgi/TAL/talgettext.py @ 3726:b11142bb2aa2 1.2.1
Password confirm field in user editing.
Pre-1.2.1-release stuff too
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sat, 07 Oct 2006 03:03:29 +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() |
