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