[CalendarServer-changes] [3312] CalendarServer/branches/users/sagen/localization-3308
source_changes at macosforge.org
source_changes at macosforge.org
Mon Nov 3 16:33:58 PST 2008
Revision: 3312
http://trac.macosforge.org/projects/calendarserver/changeset/3312
Author: sagen at apple.com
Date: 2008-11-03 16:33:58 -0800 (Mon, 03 Nov 2008)
Log Message:
-----------
Outbound iMIPs now differentiate between original invites and subsequent updates
Modified Paths:
--------------
CalendarServer/branches/users/sagen/localization-3308/locales/en/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.mo
CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.mo
CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.po
CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/mail.py
CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/test_mail.py
Added Paths:
-----------
CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py
CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py
Modified: CalendarServer/branches/users/sagen/localization-3308/locales/en/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/locales/en/LC_MESSAGES/calendarserver.po 2008-11-03 17:01:57 UTC (rev 3311)
+++ CalendarServer/branches/users/sagen/localization-3308/locales/en/LC_MESSAGES/calendarserver.po 2008-11-04 00:33:58 UTC (rev 3312)
@@ -195,10 +195,16 @@
msgid "Event invitation: %(summary)s"
msgstr ""
+msgid "Event update: %(summary)s"
+msgstr ""
+
#: mail.py:736
msgid "Event Invitation"
msgstr ""
+msgid "Event Update"
+msgstr ""
+
#: mail.py:737
msgid "Date"
msgstr ""
Modified: CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Modified: CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.po 2008-11-03 17:01:57 UTC (rev 3311)
+++ CalendarServer/branches/users/sagen/localization-3308/locales/fr/LC_MESSAGES/calendarserver.po 2008-11-04 00:33:58 UTC (rev 3312)
@@ -39,10 +39,16 @@
msgid "Event invitation: %(summary)s"
msgstr "Invitation: %(summary)s"
+msgid "Event update: %(summary)s"
+msgstr "Mise à jour de l'événement: %(summary)s"
+
#: mail.py:736
msgid "Event Invitation"
msgstr "Invitation"
+msgid "Event Update"
+msgstr "Mise à jour de l'événement"
+
#: mail.py:737
msgid "Date"
msgstr "Date"
Modified: CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Modified: CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.po 2008-11-03 17:01:57 UTC (rev 3311)
+++ CalendarServer/branches/users/sagen/localization-3308/locales/pig/LC_MESSAGES/calendarserver.po 2008-11-04 00:33:58 UTC (rev 3312)
@@ -195,10 +195,16 @@
msgid "Event invitation: %(summary)s"
msgstr "Eventway invitationway: %(summary)s"
+msgid "Event update: %(summary)s"
+msgstr "Eventway updateway: %(summary)s"
+
#: mail.py:736
msgid "Event Invitation"
-msgstr "Eventway invitationway"
+msgstr "Eventway Invitationway"
+msgid "Event Update"
+msgstr "Eventway Updateway"
+
#: mail.py:737
msgid "Date"
msgstr "Ateday"
Added: CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py (rev 0)
+++ CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py 2008-11-04 00:33:58 UTC (rev 3312)
@@ -0,0 +1,203 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# Written by Martin v. L\xF6wis <loewis at informatik.hu-berlin.de>
+
+"""Generate binary message catalog from textual translation description.
+
+This program converts a textual Uniforum-style message catalog (.po file) into
+a binary GNU catalog (.mo file). This is essentially the same function as the
+GNU msgfmt program, however, it is a simpler implementation.
+
+Usage: msgfmt.py [OPTIONS] filename.po
+
+Options:
+ -o file
+ --output-file=file
+ Specify the output file to write to. If omitted, output will go to a
+ file named filename.mo (based off the input file name).
+
+ -h
+ --help
+ Print this message and exit.
+
+ -V
+ --version
+ Display version information and exit.
+"""
+
+import sys
+import os
+import getopt
+import struct
+import array
+
+__version__ = "1.1"
+
+MESSAGES = {}
+
+
+
+def usage(code, msg=''):
+ print >> sys.stderr, __doc__
+ if msg:
+ print >> sys.stderr, msg
+ sys.exit(code)
+
+
+
+def add(id, str, fuzzy):
+ "Add a non-fuzzy translation to the dictionary."
+ global MESSAGES
+ if not fuzzy and str:
+ MESSAGES[id] = str
+
+
+
+def generate():
+ "Return the generated output."
+ global MESSAGES
+ keys = MESSAGES.keys()
+ # the keys are sorted in the .mo file
+ keys.sort()
+ offsets = []
+ ids = strs = ''
+ for id in keys:
+ # For each string, we need size and file offset. Each string is NUL
+ # terminated; the NUL does not count into the size.
+ offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
+ ids += id + '\0'
+ strs += MESSAGES[id] + '\0'
+ output = ''
+ # The header is 7 32-bit unsigned integers. We don't use hash tables, so
+ # the keys start right after the index tables.
+ # translated string.
+ keystart = 7*4+16*len(keys)
+ # and the values start after the keys
+ valuestart = keystart + len(ids)
+ koffsets = []
+ voffsets = []
+ # The string table first has the list of keys, then the list of values.
+ # Each entry has first the size of the string, then the file offset.
+ for o1, l1, o2, l2 in offsets:
+ koffsets += [l1, o1+keystart]
+ voffsets += [l2, o2+valuestart]
+ offsets = koffsets + voffsets
+ output = struct.pack("Iiiiiii",
+ 0x950412deL, # Magic
+ 0, # Version
+ len(keys), # # of entries
+ 7*4, # start of key index
+ 7*4+len(keys)*8, # start of value index
+ 0, 0) # size and offset of hash table
+ output += array.array("i", offsets).tostring()
+ output += ids
+ output += strs
+ return output
+
+
+
+def make(filename, outfile):
+ ID = 1
+ STR = 2
+
+ # Compute .mo name from .po name and arguments
+ if filename.endswith('.po'):
+ infile = filename
+ else:
+ infile = filename + '.po'
+ if outfile is None:
+ outfile = os.path.splitext(infile)[0] + '.mo'
+
+ try:
+ lines = open(infile).readlines()
+ except IOError, msg:
+ print >> sys.stderr, msg
+ sys.exit(1)
+
+ section = None
+ fuzzy = 0
+
+ # Parse the catalog
+ lno = 0
+ for l in lines:
+ lno += 1
+ # If we get a comment line after a msgstr, this is a new entry
+ if l[0] == '#' and section == STR:
+ add(msgid, msgstr, fuzzy)
+ section = None
+ fuzzy = 0
+ # Record a fuzzy mark
+ if l[:2] == '#,' and 'fuzzy' in l:
+ fuzzy = 1
+ # Skip comments
+ if l[0] == '#':
+ continue
+ # Now we are in a msgid section, output previous section
+ if l.startswith('msgid'):
+ if section == STR:
+ add(msgid, msgstr, fuzzy)
+ section = ID
+ l = l[5:]
+ msgid = msgstr = ''
+ # Now we are in a msgstr section
+ elif l.startswith('msgstr'):
+ section = STR
+ l = l[6:]
+ # Skip empty lines
+ l = l.strip()
+ if not l:
+ continue
+ # XXX: Does this always follow Python escape semantics?
+ l = eval(l)
+ if section == ID:
+ msgid += l
+ elif section == STR:
+ msgstr += l
+ else:
+ print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \
+ 'before:'
+ print >> sys.stderr, l
+ sys.exit(1)
+ # Add last entry
+ if section == STR:
+ add(msgid, msgstr, fuzzy)
+
+ # Compute output
+ output = generate()
+
+ try:
+ open(outfile,"wb").write(output)
+ except IOError,msg:
+ print >> sys.stderr, msg
+
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
+ ['help', 'version', 'output-file='])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ outfile = None
+ # parse options
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-V', '--version'):
+ print >> sys.stderr, "msgfmt.py", __version__
+ sys.exit(0)
+ elif opt in ('-o', '--output-file'):
+ outfile = arg
+ # do it
+ if not args:
+ print >> sys.stderr, 'No input file given'
+ print >> sys.stderr, "Try `msgfmt --help' for more information."
+ return
+
+ for filename in args:
+ make(filename, outfile)
+
+
+if __name__ == '__main__':
+ main()
Property changes on: CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py
___________________________________________________________________
Added: svn:executable
+ *
Added: CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py (rev 0)
+++ CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py 2008-11-04 00:33:58 UTC (rev 3312)
@@ -0,0 +1,669 @@
+#! /usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+# Originally written by Barry Warsaw <barry at zope.com>
+#
+# Minimally patched to make it even more xgettext compatible
+# by Peter Funk <pf at artcom-gmbh.de>
+#
+# 2002-11-22 J\xFCrgen Hermann <jh at web.de>
+# Added checks that _() only contains string literals, and
+# command line args are resolved to module lists, i.e. you
+# can now pass a filename, a module or package name, or a
+# directory (including globbing chars, important for Win32).
+# Made docstring fit in 80 chars wide displays using pydoc.
+#
+
+# for selftesting
+try:
+ import fintl
+ _ = fintl.gettext
+except ImportError:
+ _ = lambda s: s
+
+__doc__ = _("""pygettext -- Python equivalent of xgettext(1)
+
+Many systems (Solaris, Linux, Gnu) provide extensive tools that ease the
+internationalization of C programs. Most of these tools are independent of
+the programming language and can be used from within Python programs.
+Martin von Loewis' work[1] helps considerably in this regard.
+
+There's one problem though; xgettext is the program that scans source code
+looking for message strings, but it groks only C (or C++). Python
+introduces a few wrinkles, such as dual quoting characters, triple quoted
+strings, and raw strings. xgettext understands none of this.
+
+Enter pygettext, which uses Python's standard tokenize module to scan
+Python source code, generating .pot files identical to what GNU xgettext[2]
+generates for C and C++ code. From there, the standard GNU tools can be
+used.
+
+A word about marking Python strings as candidates for translation. GNU
+xgettext recognizes the following keywords: gettext, dgettext, dcgettext,
+and gettext_noop. But those can be a lot of text to include all over your
+code. C and C++ have a trick: they use the C preprocessor. Most
+internationalized C source includes a #define for gettext() to _() so that
+what has to be written in the source is much less. Thus these are both
+translatable strings:
+
+ gettext("Translatable String")
+ _("Translatable String")
+
+Python of course has no preprocessor so this doesn't work so well. Thus,
+pygettext searches only for _() by default, but see the -k/--keyword flag
+below for how to augment this.
+
+ [1] http://www.python.org/workshops/1997-10/proceedings/loewis.html
+ [2] http://www.gnu.org/software/gettext/gettext.html
+
+NOTE: pygettext attempts to be option and feature compatible with GNU
+xgettext where ever possible. However some options are still missing or are
+not fully implemented. Also, xgettext's use of command line switches with
+option arguments is broken, and in these cases, pygettext just defines
+additional switches.
+
+Usage: pygettext [options] inputfile ...
+
+Options:
+
+ -a
+ --extract-all
+ Extract all strings.
+
+ -d name
+ --default-domain=name
+ Rename the default output file from messages.pot to name.pot.
+
+ -E
+ --escape
+ Replace non-ASCII characters with octal escape sequences.
+
+ -D
+ --docstrings
+ Extract module, class, method, and function docstrings. These do
+ not need to be wrapped in _() markers, and in fact cannot be for
+ Python to consider them docstrings. (See also the -X option).
+
+ -h
+ --help
+ Print this help message and exit.
+
+ -k word
+ --keyword=word
+ Keywords to look for in addition to the default set, which are:
+ %(DEFAULTKEYWORDS)s
+
+ You can have multiple -k flags on the command line.
+
+ -K
+ --no-default-keywords
+ Disable the default set of keywords (see above). Any keywords
+ explicitly added with the -k/--keyword option are still recognized.
+
+ --no-location
+ Do not write filename/lineno location comments.
+
+ -n
+ --add-location
+ Write filename/lineno location comments indicating where each
+ extracted string is found in the source. These lines appear before
+ each msgid. The style of comments is controlled by the -S/--style
+ option. This is the default.
+
+ -o filename
+ --output=filename
+ Rename the default output file from messages.pot to filename. If
+ filename is `-' then the output is sent to standard out.
+
+ -p dir
+ --output-dir=dir
+ Output files will be placed in directory dir.
+
+ -S stylename
+ --style stylename
+ Specify which style to use for location comments. Two styles are
+ supported:
+
+ Solaris # File: filename, line: line-number
+ GNU #: filename:line
+
+ The style name is case insensitive. GNU style is the default.
+
+ -v
+ --verbose
+ Print the names of the files being processed.
+
+ -V
+ --version
+ Print the version of pygettext and exit.
+
+ -w columns
+ --width=columns
+ Set width of output to columns.
+
+ -x filename
+ --exclude-file=filename
+ Specify a file that contains a list of strings that are not be
+ extracted from the input files. Each string to be excluded must
+ appear on a line by itself in the file.
+
+ -X filename
+ --no-docstrings=filename
+ Specify a file that contains a list of files (one per line) that
+ should not have their docstrings extracted. This is only useful in
+ conjunction with the -D option above.
+
+If `inputfile' is -, standard input is read.
+""")
+
+import os
+import imp
+import sys
+import glob
+import time
+import getopt
+import token
+import tokenize
+import operator
+
+__version__ = '1.5'
+
+default_keywords = ['_']
+DEFAULTKEYWORDS = ', '.join(default_keywords)
+
+EMPTYSTRING = ''
+
+
+
+# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
+# there.
+pot_header = _('''\
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\\n"
+"POT-Creation-Date: %(time)s\\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\\n"
+"Language-Team: LANGUAGE <LL at li.org>\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=CHARSET\\n"
+"Content-Transfer-Encoding: ENCODING\\n"
+"Generated-By: pygettext.py %(version)s\\n"
+
+''')
+
+
+def usage(code, msg=''):
+ print >> sys.stderr, __doc__ % globals()
+ if msg:
+ print >> sys.stderr, msg
+ sys.exit(code)
+
+
+
+escapes = []
+
+def make_escapes(pass_iso8859):
+ global escapes
+ if pass_iso8859:
+ # Allow iso-8859 characters to pass through so that e.g. 'msgid
+ # "H\xF6he"' would result not result in 'msgid "H\366he"'. Otherwise we
+ # escape any character outside the 32..126 range.
+ mod = 128
+ else:
+ mod = 256
+ for i in range(256):
+ if 32 <= (i % mod) <= 126:
+ escapes.append(chr(i))
+ else:
+ escapes.append("\\%03o" % i)
+ escapes[ord('\\')] = '\\\\'
+ escapes[ord('\t')] = '\\t'
+ escapes[ord('\r')] = '\\r'
+ escapes[ord('\n')] = '\\n'
+ escapes[ord('\"')] = '\\"'
+
+
+def escape(s):
+ global escapes
+ s = list(s)
+ for i in range(len(s)):
+ s[i] = escapes[ord(s[i])]
+ return EMPTYSTRING.join(s)
+
+
+def safe_eval(s):
+ # unwrap quotes, safely
+ return eval(s, {'__builtins__':{}}, {})
+
+
+def normalize(s):
+ # This converts the various Python string types into a format that is
+ # appropriate for .po files, namely much closer to C style.
+ lines = s.split('\n')
+ if len(lines) == 1:
+ s = '"' + escape(s) + '"'
+ else:
+ if not lines[-1]:
+ del lines[-1]
+ lines[-1] = lines[-1] + '\n'
+ for i in range(len(lines)):
+ lines[i] = escape(lines[i])
+ lineterm = '\\n"\n"'
+ s = '""\n"' + lineterm.join(lines) + '"'
+ return s
+
+
+def containsAny(str, set):
+ """Check whether 'str' contains ANY of the chars in 'set'"""
+ return 1 in [c in str for c in set]
+
+
+def _visit_pyfiles(list, dirname, names):
+ """Helper for getFilesForName()."""
+ # get extension for python source files
+ if not globals().has_key('_py_ext'):
+ global _py_ext
+ _py_ext = [triple[0] for triple in imp.get_suffixes()
+ if triple[2] == imp.PY_SOURCE][0]
+
+ # don't recurse into CVS directories
+ if 'CVS' in names:
+ names.remove('CVS')
+
+ # add all *.py files to list
+ list.extend(
+ [os.path.join(dirname, file) for file in names
+ if os.path.splitext(file)[1] == _py_ext]
+ )
+
+
+def _get_modpkg_path(dotted_name, pathlist=None):
+ """Get the filesystem path for a module or a package.
+
+ Return the file system path to a file for a module, and to a directory for
+ a package. Return None if the name is not found, or is a builtin or
+ extension module.
+ """
+ # split off top-most name
+ parts = dotted_name.split('.', 1)
+
+ if len(parts) > 1:
+ # we have a dotted path, import top-level package
+ try:
+ file, pathname, description = imp.find_module(parts[0], pathlist)
+ if file: file.close()
+ except ImportError:
+ return None
+
+ # check if it's indeed a package
+ if description[2] == imp.PKG_DIRECTORY:
+ # recursively handle the remaining name parts
+ pathname = _get_modpkg_path(parts[1], [pathname])
+ else:
+ pathname = None
+ else:
+ # plain name
+ try:
+ file, pathname, description = imp.find_module(
+ dotted_name, pathlist)
+ if file:
+ file.close()
+ if description[2] not in [imp.PY_SOURCE, imp.PKG_DIRECTORY]:
+ pathname = None
+ except ImportError:
+ pathname = None
+
+ return pathname
+
+
+def getFilesForName(name):
+ """Get a list of module files for a filename, a module or package name,
+ or a directory.
+ """
+ if not os.path.exists(name):
+ # check for glob chars
+ if containsAny(name, "*?[]"):
+ files = glob.glob(name)
+ list = []
+ for file in files:
+ list.extend(getFilesForName(file))
+ return list
+
+ # try to find module or package
+ name = _get_modpkg_path(name)
+ if not name:
+ return []
+
+ if os.path.isdir(name):
+ # find all python files in directory
+ list = []
+ os.path.walk(name, _visit_pyfiles, list)
+ return list
+ elif os.path.exists(name):
+ # a single file
+ return [name]
+
+ return []
+
+
+class TokenEater:
+ def __init__(self, options):
+ self.__options = options
+ self.__messages = {}
+ self.__state = self.__waiting
+ self.__data = []
+ self.__lineno = -1
+ self.__freshmodule = 1
+ self.__curfile = None
+
+ def __call__(self, ttype, tstring, stup, etup, line):
+ # dispatch
+## import token
+## print >> sys.stderr, 'ttype:', token.tok_name[ttype], \
+## 'tstring:', tstring
+ self.__state(ttype, tstring, stup[0])
+
+ def __waiting(self, ttype, tstring, lineno):
+ opts = self.__options
+ # Do docstring extractions, if enabled
+ if opts.docstrings and not opts.nodocstrings.get(self.__curfile):
+ # module docstring?
+ if self.__freshmodule:
+ if ttype == tokenize.STRING:
+ self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
+ self.__freshmodule = 0
+ elif ttype not in (tokenize.COMMENT, tokenize.NL):
+ self.__freshmodule = 0
+ return
+ # class docstring?
+ if ttype == tokenize.NAME and tstring in ('class', 'def'):
+ self.__state = self.__suiteseen
+ return
+ if ttype == tokenize.NAME and tstring in opts.keywords:
+ self.__state = self.__keywordseen
+
+ def __suiteseen(self, ttype, tstring, lineno):
+ # ignore anything until we see the colon
+ if ttype == tokenize.OP and tstring == ':':
+ self.__state = self.__suitedocstring
+
+ def __suitedocstring(self, ttype, tstring, lineno):
+ # ignore any intervening noise
+ if ttype == tokenize.STRING:
+ self.__addentry(safe_eval(tstring), lineno, isdocstring=1)
+ self.__state = self.__waiting
+ elif ttype not in (tokenize.NEWLINE, tokenize.INDENT,
+ tokenize.COMMENT):
+ # there was no class docstring
+ self.__state = self.__waiting
+
+ def __keywordseen(self, ttype, tstring, lineno):
+ if ttype == tokenize.OP and tstring == '(':
+ self.__data = []
+ self.__lineno = lineno
+ self.__state = self.__openseen
+ else:
+ self.__state = self.__waiting
+
+ def __openseen(self, ttype, tstring, lineno):
+ if ttype == tokenize.OP and tstring == ')':
+ # We've seen the last of the translatable strings. Record the
+ # line number of the first line of the strings and update the list
+ # of messages seen. Reset state for the next batch. If there
+ # were no strings inside _(), then just ignore this entry.
+ if self.__data:
+ self.__addentry(EMPTYSTRING.join(self.__data))
+ self.__state = self.__waiting
+ elif ttype == tokenize.STRING:
+ self.__data.append(safe_eval(tstring))
+ elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT,
+ token.NEWLINE, tokenize.NL]:
+ # warn if we see anything else than STRING or whitespace
+ print >> sys.stderr, _(
+ '*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"'
+ ) % {
+ 'token': tstring,
+ 'file': self.__curfile,
+ 'lineno': self.__lineno
+ }
+ self.__state = self.__waiting
+
+ def __addentry(self, msg, lineno=None, isdocstring=0):
+ if lineno is None:
+ lineno = self.__lineno
+ if not msg in self.__options.toexclude:
+ entry = (self.__curfile, lineno)
+ self.__messages.setdefault(msg, {})[entry] = isdocstring
+
+ def set_filename(self, filename):
+ self.__curfile = filename
+ self.__freshmodule = 1
+
+ def write(self, fp):
+ options = self.__options
+ timestamp = time.strftime('%Y-%m-%d %H:%M+%Z')
+ # The time stamp in the header doesn't have the same format as that
+ # generated by xgettext...
+ print >> fp, pot_header % {'time': timestamp, 'version': __version__}
+ # Sort the entries. First sort each particular entry's keys, then
+ # sort all the entries by their first item.
+ reverse = {}
+ for k, v in self.__messages.items():
+ keys = v.keys()
+ keys.sort()
+ reverse.setdefault(tuple(keys), []).append((k, v))
+ rkeys = reverse.keys()
+ rkeys.sort()
+ for rkey in rkeys:
+ rentries = reverse[rkey]
+ rentries.sort()
+ for k, v in rentries:
+ isdocstring = 0
+ # If the entry was gleaned out of a docstring, then add a
+ # comment stating so. This is to aid translators who may wish
+ # to skip translating some unimportant docstrings.
+ if reduce(operator.__add__, v.values()):
+ isdocstring = 1
+ # k is the message string, v is a dictionary-set of (filename,
+ # lineno) tuples. We want to sort the entries in v first by
+ # file name and then by line number.
+ v = v.keys()
+ v.sort()
+ if not options.writelocations:
+ pass
+ # location comments are different b/w Solaris and GNU:
+ elif options.locationstyle == options.SOLARIS:
+ for filename, lineno in v:
+ d = {'filename': filename, 'lineno': lineno}
+ print >>fp, _(
+ '# File: %(filename)s, line: %(lineno)d') % d
+ elif options.locationstyle == options.GNU:
+ # fit as many locations on one line, as long as the
+ # resulting line length doesn't exceeds 'options.width'
+ locline = '#:'
+ for filename, lineno in v:
+ d = {'filename': filename, 'lineno': lineno}
+ s = _(' %(filename)s:%(lineno)d') % d
+ if len(locline) + len(s) <= options.width:
+ locline = locline + s
+ else:
+ print >> fp, locline
+ locline = "#:" + s
+ if len(locline) > 2:
+ print >> fp, locline
+ if isdocstring:
+ print >> fp, '#, docstring'
+ print >> fp, 'msgid', normalize(k)
+ print >> fp, 'msgstr ""\n'
+
+
+
+def main():
+ global default_keywords
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:],
+ 'ad:DEhk:Kno:p:S:Vvw:x:X:',
+ ['extract-all', 'default-domain=', 'escape', 'help',
+ 'keyword=', 'no-default-keywords',
+ 'add-location', 'no-location', 'output=', 'output-dir=',
+ 'style=', 'verbose', 'version', 'width=', 'exclude-file=',
+ 'docstrings', 'no-docstrings',
+ ])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ # for holding option values
+ class Options:
+ # constants
+ GNU = 1
+ SOLARIS = 2
+ # defaults
+ extractall = 0 # FIXME: currently this option has no effect at all.
+ escape = 0
+ keywords = []
+ outpath = ''
+ outfile = 'messages.pot'
+ writelocations = 1
+ locationstyle = GNU
+ verbose = 0
+ width = 78
+ excludefilename = ''
+ docstrings = 0
+ nodocstrings = {}
+
+ options = Options()
+ locations = {'gnu' : options.GNU,
+ 'solaris' : options.SOLARIS,
+ }
+
+ # parse options
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-a', '--extract-all'):
+ options.extractall = 1
+ elif opt in ('-d', '--default-domain'):
+ options.outfile = arg + '.pot'
+ elif opt in ('-E', '--escape'):
+ options.escape = 1
+ elif opt in ('-D', '--docstrings'):
+ options.docstrings = 1
+ elif opt in ('-k', '--keyword'):
+ options.keywords.append(arg)
+ elif opt in ('-K', '--no-default-keywords'):
+ default_keywords = []
+ elif opt in ('-n', '--add-location'):
+ options.writelocations = 1
+ elif opt in ('--no-location',):
+ options.writelocations = 0
+ elif opt in ('-S', '--style'):
+ options.locationstyle = locations.get(arg.lower())
+ if options.locationstyle is None:
+ usage(1, _('Invalid value for --style: %s') % arg)
+ elif opt in ('-o', '--output'):
+ options.outfile = arg
+ elif opt in ('-p', '--output-dir'):
+ options.outpath = arg
+ elif opt in ('-v', '--verbose'):
+ options.verbose = 1
+ elif opt in ('-V', '--version'):
+ print _('pygettext.py (xgettext for Python) %s') % __version__
+ sys.exit(0)
+ elif opt in ('-w', '--width'):
+ try:
+ options.width = int(arg)
+ except ValueError:
+ usage(1, _('--width argument must be an integer: %s') % arg)
+ elif opt in ('-x', '--exclude-file'):
+ options.excludefilename = arg
+ elif opt in ('-X', '--no-docstrings'):
+ fp = open(arg)
+ try:
+ while 1:
+ line = fp.readline()
+ if not line:
+ break
+ options.nodocstrings[line[:-1]] = 1
+ finally:
+ fp.close()
+
+ # calculate escapes
+ make_escapes(options.escape)
+
+ # calculate all keywords
+ options.keywords.extend(default_keywords)
+
+ # initialize list of strings to exclude
+ if options.excludefilename:
+ try:
+ fp = open(options.excludefilename)
+ options.toexclude = fp.readlines()
+ fp.close()
+ except IOError:
+ print >> sys.stderr, _(
+ "Can't read --exclude-file: %s") % options.excludefilename
+ sys.exit(1)
+ else:
+ options.toexclude = []
+
+ # resolve args to module lists
+ expanded = []
+ for arg in args:
+ if arg == '-':
+ expanded.append(arg)
+ else:
+ expanded.extend(getFilesForName(arg))
+ args = expanded
+
+ # slurp through all the files
+ eater = TokenEater(options)
+ for filename in args:
+ if filename == '-':
+ if options.verbose:
+ print _('Reading standard input')
+ fp = sys.stdin
+ closep = 0
+ else:
+ if options.verbose:
+ print _('Working on %s') % filename
+ fp = open(filename)
+ closep = 1
+ try:
+ eater.set_filename(filename)
+ try:
+ tokenize.tokenize(fp.readline, eater)
+ except tokenize.TokenError, e:
+ print >> sys.stderr, '%s: %s, line %d, column %d' % (
+ e[0], filename, e[1][0], e[1][1])
+ finally:
+ if closep:
+ fp.close()
+
+ # write the output
+ if options.outfile == '-':
+ fp = sys.stdout
+ closep = 0
+ else:
+ if options.outpath:
+ options.outfile = os.path.join(options.outpath, options.outfile)
+ fp = open(options.outfile, 'w')
+ closep = 1
+ try:
+ eater.write(fp)
+ finally:
+ if closep:
+ fp.close()
+
+
+if __name__ == '__main__':
+ main()
+ # some more test strings
+ _(u'a unicode string')
+ # this one creates a warning
+ _('*** Seen unexpected token "%(token)s"') % {'token': 'test'}
+ _('more' 'than' 'one' 'string')
Property changes on: CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py
___________________________________________________________________
Added: svn:executable
+ *
Modified: CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/mail.py 2008-11-03 17:01:57 UTC (rev 3311)
+++ CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/mail.py 2008-11-04 00:33:58 UTC (rev 3312)
@@ -292,7 +292,7 @@
Token Database:
- ROW: TOKEN, ORGANIZER, ATTENDEE
+ ROW: TOKEN, ORGANIZER, ATTENDEE, ICALUID, DATESTAMP
"""
@@ -306,14 +306,14 @@
path = os.path.join(path, MailGatewayTokensDatabase.dbFilename)
super(MailGatewayTokensDatabase, self).__init__(path, True)
- def createToken(self, organizer, attendee, token=None):
+ def createToken(self, organizer, attendee, icaluid, token=None):
if token is None:
token = str(uuid.uuid4())
self._db_execute(
"""
- insert into TOKENS (TOKEN, ORGANIZER, ATTENDEE)
- values (:1, :2, :3)
- """, token, organizer, attendee
+ insert into TOKENS (TOKEN, ORGANIZER, ATTENDEE, ICALUID, DATESTAMP)
+ values (:1, :2, :3, :4, :5)
+ """, token, organizer, attendee, icaluid, datetime.date.today()
)
self._db_commit()
return token
@@ -322,7 +322,7 @@
results = list(
self._db_execute(
"""
- select ORGANIZER, ATTENDEE from TOKENS
+ select ORGANIZER, ATTENDEE, ICALUID from TOKENS
where TOKEN = :1
""", token
)
@@ -333,12 +333,12 @@
return results[0]
- def getToken(self, organizer, attendee):
+ def getToken(self, organizer, attendee, icaluid):
token = self._db_value_for_sql(
"""
select TOKEN from TOKENS
- where ORGANIZER = :1 and ATTENDEE = :2
- """, organizer, attendee
+ where ORGANIZER = :1 and ATTENDEE = :2 and ICALUID = :3
+ """, organizer, attendee, icaluid
)
return token
@@ -348,7 +348,16 @@
delete from TOKENS where TOKEN = :1
""", token
)
+ self._db_commit()
+ def purgeOldTokens(self, before):
+ self._db_execute(
+ """
+ delete from TOKENS where DATESTAMP < :1
+ """, before
+ )
+ self._db_commit()
+
def _db_version(self):
"""
@return: the schema version assigned to this index.
@@ -375,7 +384,9 @@
create table TOKENS (
TOKEN text,
ORGANIZER text,
- ATTENDEE text
+ ATTENDEE text,
+ ICALUID text,
+ DATESTAMP date
)
"""
)
@@ -503,6 +514,8 @@
if dataRoot is None:
dataRoot = config.DataRoot
self.db = MailGatewayTokensDatabase(dataRoot)
+ self.db.purgeOldTokens(datetime.date.today() -
+ datetime.timedelta(days=365))
def checkDSN(self, message):
# returns (isDSN, Action, icalendar attachment)
@@ -567,9 +580,10 @@
self.log_error("Mail gateway found a token (%s) but didn't recognize it in DSN %s" % (token, msgId))
return
- organizer, attendee = result
+ organizer, attendee, icaluid = result
organizer = str(organizer)
attendee = str(attendee)
+ icaluid = str(icaluid)
calendar.removeAllButOneAttendee(attendee)
calendar.getOrganizerProperty().setValue(organizer)
for comp in calendar.subcomponents():
@@ -617,9 +631,10 @@
self.log_error("Mail gateway found a token (%s) but didn't recognize it in message %s" % (token, msg['Message-ID']))
return
- organizer, attendee = result
+ organizer, attendee, icaluid = result
organizer = str(organizer)
attendee = str(attendee)
+ icaluid = str(icaluid)
calendar.removeAllButOneAttendee(attendee)
organizerProperty = calendar.getOrganizerProperty()
if organizerProperty is None:
@@ -660,12 +675,20 @@
def outbound(self, organizer, attendee, calendar, language='en'):
# create token, send email
- token = self.db.getToken(organizer, attendee)
+
+ component = calendar.masterComponent()
+ if component is None:
+ component = calendar.mainComponent(True)
+ icaluid = component.propertyValue("UID")
+
+ token = self.db.getToken(organizer, attendee, icaluid)
if token is None:
- token = self.db.createToken(organizer, attendee)
- self.log_debug("Mail gateway created token %s for %s (organizer) and %s (attendee)" % (token, organizer, attendee))
+ token = self.db.createToken(organizer, attendee, icaluid)
+ self.log_debug("Mail gateway created token %s for %s (organizer), %s (attendee) and %s (icaluid)" % (token, organizer, attendee, icaluid))
+ newInvitation = True
else:
- self.log_debug("Mail gateway reusing token %s for %s (organizer) and %s (attendee)" % (token, organizer, attendee))
+ self.log_debug("Mail gateway reusing token %s for %s (organizer), %s (attendee) and %s (icaluid)" % (token, organizer, attendee, icaluid))
+ newInvitation = False
settings = config.Scheduling['iMIP']['Sending']
fullServerAddress = settings['Address']
@@ -721,8 +744,8 @@
raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (attendee,))
attendee = attendee[7:]
- msgId, message = self.generateEmail(calendar, orgEmail, orgCN,
- attendees, formattedFrom, addressWithToken, attendee,
+ msgId, message = self.generateEmail(newInvitation, calendar, orgEmail,
+ orgCN, attendees, formattedFrom, addressWithToken, attendee,
language=language)
self.log_debug("Sending: %s" % (message,))
@@ -741,8 +764,8 @@
deferred.addErrback(_failure, msgId, fromAddr, toAddr)
- def generateEmail(self, calendar, orgEmail, orgCN, attendees, fromAddress,
- replyToAddress, toAddress, language='en'):
+ def generateEmail(self, newInvitation, calendar, orgEmail, orgCN,
+ attendees, fromAddress, replyToAddress, toAddress, language='en'):
details = self.getEventDetails(calendar, language=language)
@@ -761,8 +784,13 @@
msg["Message-ID"] = msgId
cancelled = (calendar.propertyValue("METHOD") == "CANCEL")
- formatString = (_("Event cancelled: %(summary)s") if cancelled else
- _("Event invitation: %(summary)s"))
+ if cancelled:
+ formatString = _("Event cancelled: %(summary)s")
+ elif newInvitation:
+ formatString = _("Event invitation: %(summary)s")
+ else:
+ formatString = _("Event update: %(summary)s")
+
details['subject'] = msg['Subject'] = formatString % {
'summary' : details['summary']
}
@@ -771,7 +799,11 @@
msg.attach(msgAlt)
# Get localized labels
- details['inviteLabel'] = _("Event Invitation")
+ if newInvitation:
+ details['inviteLabel'] = _("Event Invitation")
+ else:
+ details['inviteLabel'] = _("Event Update")
+
details['dateLabel'] = _("Date")
details['timeLabel'] = _("Time")
details['durationLabel'] = _("Duration")
Modified: CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/test_mail.py
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/test_mail.py 2008-11-03 17:01:57 UTC (rev 3311)
+++ CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/test_mail.py 2008-11-04 00:33:58 UTC (rev 3312)
@@ -57,7 +57,7 @@
# Make sure a known token *is* processed
token = self.handler.db.createToken("mailto:user01 at example.com",
- "mailto:user02 at example.com")
+ "mailto:user02 at example.com", "1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C")
calBody = template % token
organizer, attendee, calendar, msgId = self.handler.processDSN(calBody,
"xyzzy", echo)
@@ -78,7 +78,7 @@
self.assertEquals(result, None)
# Make sure a known token *is* processed
- self.handler.db.createToken("mailto:user01 at example.com", "mailto:xyzzy at example.com", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f")
+ self.handler.db.createToken("mailto:user01 at example.com", "mailto:xyzzy at example.com", icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f")
organizer, attendee, calendar, msgId = self.handler.processReply(msg,
echo)
self.assertEquals(organizer, 'mailto:user01 at example.com')
@@ -90,7 +90,7 @@
file(os.path.join(self.dataDir, 'reply_missing_organizer')).read()
)
# stick the token in the database first
- self.handler.db.createToken("mailto:user01 at example.com", "mailto:xyzzy at example.com", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f")
+ self.handler.db.createToken("mailto:user01 at example.com", "mailto:xyzzy at example.com", icaluid="1E71F9C8-AEDA-48EB-98D0-76E898F6BB5C", token="d7cdf68d-8b73-4df1-ad3b-f08002fb285f")
organizer, attendee, calendar, msgId = self.handler.processReply(msg,
echo)
@@ -105,9 +105,9 @@
def test_tokens(self):
self.assertEquals(self.db.lookupByToken("xyzzy"), None)
- token = self.db.createToken("organizer", "attendee")
- self.assertEquals(self.db.getToken("organizer", "attendee"), token)
+ token = self.db.createToken("organizer", "attendee", "icaluid")
+ self.assertEquals(self.db.getToken("organizer", "attendee", "icaluid"), token)
self.assertEquals(self.db.lookupByToken(token),
- ("organizer", "attendee"))
+ ("organizer", "attendee", "icaluid"))
self.db.deleteToken(token)
self.assertEquals(self.db.lookupByToken(token), None)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081103/66300bae/attachment-0001.html>
More information about the calendarserver-changes
mailing list