[CalendarServer-changes] [3325] CalendarServer/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Nov 5 17:45:14 PST 2008
Revision: 3325
http://trac.macosforge.org/projects/calendarserver/changeset/3325
Author: sagen at apple.com
Date: 2008-11-05 17:45:12 -0800 (Wed, 05 Nov 2008)
Log Message:
-----------
Landing localized emails branch
Modified Paths:
--------------
CalendarServer/trunk/conf/caldavd-test.plist
CalendarServer/trunk/locales/en/LC_MESSAGES/calendarserver.mo
CalendarServer/trunk/locales/en/LC_MESSAGES/calendarserver.po
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/ical.py
CalendarServer/trunk/twistedcaldav/mail.py
CalendarServer/trunk/twistedcaldav/test/test_mail.py
Added Paths:
-----------
CalendarServer/trunk/locales/
CalendarServer/trunk/locales/en/
CalendarServer/trunk/locales/en/LC_MESSAGES/
CalendarServer/trunk/support/msgfmt.py
CalendarServer/trunk/support/pygettext.py
CalendarServer/trunk/twistedcaldav/test/data/locales/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po
CalendarServer/trunk/twistedcaldav/test/test_localization.py
Removed Paths:
-------------
CalendarServer/trunk/locales/en/
CalendarServer/trunk/locales/en/LC_MESSAGES/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo
CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo
CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po
Property Changed:
----------------
CalendarServer/trunk/locales/en/LC_MESSAGES/calendarserver.mo
Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist 2008-11-06 01:28:37 UTC (rev 3324)
+++ CalendarServer/trunk/conf/caldavd-test.plist 2008-11-06 01:45:12 UTC (rev 3325)
@@ -638,6 +638,13 @@
<string>logs/caldavd-pydir.sock</string>
</dict>
+ <key>Localization</key>
+ <dict>
+ <key>LocalesDirectory</key>
+ <string>locales</string>
+ <key>Language</key>
+ <string>en</string>
+ </dict>
</dict>
</plist>
Modified: CalendarServer/trunk/locales/en/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Copied: CalendarServer/trunk/support/msgfmt.py (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/support/msgfmt.py)
===================================================================
--- CalendarServer/trunk/support/msgfmt.py (rev 0)
+++ CalendarServer/trunk/support/msgfmt.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -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()
Copied: CalendarServer/trunk/support/pygettext.py (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/support/pygettext.py)
===================================================================
--- CalendarServer/trunk/support/pygettext.py (rev 0)
+++ CalendarServer/trunk/support/pygettext.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -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')
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2008-11-06 01:28:37 UTC (rev 3324)
+++ CalendarServer/trunk/twistedcaldav/config.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -233,6 +233,9 @@
"Password" : "", # For account receving mail
},
"AddressPatterns" : [], # Reg-ex patterns to match iMIP-able calendar user addresses
+ "MailTemplatesDirectory": "/usr/share/caldavd/templates", # Directory containing HTML templates for email invitations (invite.html, cancel.html)
+ "MailIconsDirectory": "/usr/share/caldavd/images/mail", # Directory containing language-specific subdirectories containing date-specific icons for email invitations (cal-icon-mm-dd.png)
+ "InvitationDaysToLive" : 90, # How many days invitations are valid
},
},
@@ -288,7 +291,17 @@
"IdleConnectionTimeOut": 15,
"UIDReservationTimeOut": 30 * 60,
+
#
+ # Localization
+ #
+ "Localization" : {
+ "LocalesDirectory" : "/usr/share/caldavd/locales",
+ "Language" : "en",
+ },
+
+
+ #
# Implementation details
#
# The following are specific to how the server is built, and useful
Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py 2008-11-06 01:28:37 UTC (rev 3324)
+++ CalendarServer/trunk/twistedcaldav/ical.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -1255,6 +1255,25 @@
return results
+ def getAllAttendeeProperties(self):
+ """
+ Yield all attendees as Property objects. Works on either a VCALENDAR or
+ on a component.
+ @return: a generator yielding Property objects
+ """
+
+ # Extract appropriate sub-component if this is a VCALENDAR
+ if self.name() == "VCALENDAR":
+ for component in self.subcomponents():
+ if component.name() != "VTIMEZONE":
+ for attendee in component.getAllAttendeeProperties():
+ yield attendee
+ else:
+ # Find the primary subcomponent
+ for attendee in self.properties("ATTENDEE"):
+ yield attendee
+
+
def getMaskUID(self):
"""
Get the X-CALENDARSEREVR-MASK-UID value. Works on either a VCALENDAR or on a component.
Modified: CalendarServer/trunk/twistedcaldav/mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/mail.py 2008-11-06 01:28:37 UTC (rev 3324)
+++ CalendarServer/trunk/twistedcaldav/mail.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -18,6 +18,7 @@
Mail Gateway for Calendar Server
"""
+from __future__ import with_statement
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
@@ -41,7 +42,9 @@
from twistedcaldav.log import Logger, LoggingMixIn
from twistedcaldav.resource import CalDAVResource
from twistedcaldav.scheduling.scheduler import IMIPScheduler
+from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
from twistedcaldav.sql import AbstractSQLDatabase
+from twistedcaldav.localization import translationTo
from zope.interface import implements
@@ -260,6 +263,8 @@
log.debug("Injecting to %s: %s %s" % (url, str(headers), data))
factory = client.HTTPClientFactory(url, method='POST', headers=headers,
postdata=data, agent="iMIP gateway")
+ factory.noisy = False
+
if useSSL:
reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
else:
@@ -287,7 +292,7 @@
Token Database:
- ROW: TOKEN, ORGANIZER, ATTENDEE
+ ROW: TOKEN, ORGANIZER, ATTENDEE, ICALUID, DATESTAMP
"""
@@ -301,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
@@ -317,7 +322,7 @@
results = list(
self._db_execute(
"""
- select ORGANIZER, ATTENDEE from TOKENS
+ select ORGANIZER, ATTENDEE, ICALUID from TOKENS
where TOKEN = :1
""", token
)
@@ -328,13 +333,20 @@
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
)
+ if token is not None:
+ # update the datestamp on the token to keep it from being purged
+ self._db_execute(
+ """
+ update TOKENS set DATESTAMP = :1 WHERE TOKEN = :2
+ """, datetime.date.today(), token
+ )
return token
def deleteToken(self, token):
@@ -343,7 +355,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.
@@ -370,7 +391,9 @@
create table TOKENS (
TOKEN text,
ORGANIZER text,
- ATTENDEE text
+ ATTENDEE text,
+ ICALUID text,
+ DATESTAMP date
)
"""
)
@@ -480,8 +503,9 @@
# Compute token, add to db, generate email and send it
calendar = ical.Component.fromString(request.content.read())
headers = request.getAllHeaders()
+ language = config.Localization["Language"]
self.mailer.outbound(headers['originator'], headers['recipient'],
- calendar)
+ calendar, language=language)
# TODO: what to return?
return """
@@ -497,6 +521,9 @@
if dataRoot is None:
dataRoot = config.DataRoot
self.db = MailGatewayTokensDatabase(dataRoot)
+ days = config.Scheduling['iMIP']['InvitationDaysToLive']
+ self.db.purgeOldTokens(datetime.date.today() -
+ datetime.timedelta(days=days))
def checkDSN(self, message):
# returns (isDSN, Action, icalendar attachment)
@@ -561,9 +588,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():
@@ -576,7 +604,7 @@
# TODO: what to do in this case?
pass
- self.log_error("Mail gateway processing DSN %s" % (msgId,))
+ self.log_warn("Mail gateway processing DSN %s" % (msgId,))
return fn(organizer, attendee, calendar, msgId)
def processReply(self, msg, fn):
@@ -611,9 +639,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:
@@ -627,39 +656,71 @@
def inbound(self, message, fn=injectMessage):
- msg = email.message_from_string(message)
+ try:
+ msg = email.message_from_string(message)
- isDSN, action, calBody = self.checkDSN(msg)
- if isDSN:
- if action == 'failed' and calBody:
- # This is a DSN we can handle
- return self.processDSN(calBody, msg['Message-ID'], fn)
- else:
- # It's a DSN without enough to go on
- self.log_error("Mail gateway can't process DSN %s" % (msg['Message-ID'],))
- return
+ isDSN, action, calBody = self.checkDSN(msg)
+ if isDSN:
+ if action == 'failed' and calBody:
+ # This is a DSN we can handle
+ return self.processDSN(calBody, msg['Message-ID'], fn)
+ else:
+ # It's a DSN without enough to go on
+ self.log_error("Mail gateway can't process DSN %s" % (msg['Message-ID'],))
+ return
- self.log_info("Mail gateway received message %s from %s to %s" %
- (msg['Message-ID'], msg['From'], msg['To']))
+ self.log_info("Mail gateway received message %s from %s to %s" %
+ (msg['Message-ID'], msg['From'], msg['To']))
- return self.processReply(msg, fn)
+ return self.processReply(msg, fn)
+ except Exception, e:
+ # Don't let a failure of any kind stop us
+ self.log_error("Failed to process message: %s" % (e,))
- def outbound(self, organizer, attendee, calendar):
+
+
+ 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_info("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_info("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']
name, serverAddress = email.utils.parseaddr(fullServerAddress)
pre, post = serverAddress.split('@')
addressWithToken = "%s+%s@%s" % (pre, token, post)
+
+ attendees = []
+ for attendeeProp in calendar.getAllAttendeeProperties():
+ params = attendeeProp.params()
+ cutype = params.get('CUTYPE', (None,))[0]
+ if cutype == "INDIVIDUAL":
+ cn = params.get("CN", (None,))[0]
+ cuaddr = normalizeCUAddr(attendeeProp.value())
+ if cuaddr.startswith("mailto:"):
+ mailto = cuaddr[7:]
+ if not cn:
+ cn = mailto
+ else:
+ mailto = None
+
+ if cn or mailto:
+ attendees.append( (cn, mailto) )
+
calendar.getOrganizerProperty().setValue("mailto:%s" %
(addressWithToken,))
@@ -667,29 +728,34 @@
if organizerAttendee is not None:
organizerAttendee.setValue("mailto:%s" % (addressWithToken,))
- msgId, message = self._generateTemplateMessage(calendar, organizer)
# The email's From will include the organizer's real name email
# address if available. Otherwise it will be the server's email
# address (without # + addressing)
if organizer.startswith("mailto:"):
- fromAddr = organizer[7:]
+ orgEmail = fromAddr = organizer[7:]
else:
fromAddr = serverAddress
- cn = calendar.getOrganizerProperty().params().get('CN',
- ['Calendar Server'])[0]
+ orgEmail = None
+ cn = calendar.getOrganizerProperty().params().get('CN', (None,))[0]
+ if cn is None:
+ cn = 'Calendar Server'
+ orgCN = orgEmail
+ else:
+ orgCN = cn
formattedFrom = "%s <%s>" % (cn, fromAddr)
- message = message.replace("${fromaddress}", formattedFrom)
# Reply-to address will be the server+token address
- message = message.replace("${replytoaddress}", addressWithToken)
toAddr = attendee
if not attendee.startswith("mailto:"):
raise ValueError("ATTENDEE address '%s' must be mailto: for iMIP operation." % (attendee,))
attendee = attendee[7:]
- message = message.replace("${toaddress}", attendee)
+ msgId, message = self.generateEmail(newInvitation, calendar, orgEmail,
+ orgCN, attendees, formattedFrom, addressWithToken, attendee,
+ language=language)
+
self.log_debug("Sending: %s" % (message,))
def _success(result, msgId, fromAddr, toAddr):
self.log_info("Mail gateway sent message %s from %s to %s" %
@@ -706,62 +772,202 @@
deferred.addErrback(_failure, msgId, fromAddr, toAddr)
- def _generateTemplateMessage(self, calendar, organizer):
+ def generateEmail(self, newInvitation, calendar, orgEmail, orgCN,
+ attendees, fromAddress, replyToAddress, toAddress, language='en'):
- title, summary = self._generateCalendarSummary(calendar, organizer)
+ details = self.getEventDetails(calendar, language=language)
- msg = MIMEMultipart()
- msg["From"] = "${fromaddress}"
- msg["Reply-To"] = "${replytoaddress}"
- msg["To"] = "${toaddress}"
- msg["Date"] = rfc822date()
- msgId = messageid()
- msg["Message-ID"] = msgId
+ iconDir = config.Scheduling["iMIP"]["MailIconsDirectory"].rstrip("/")
+ iconName = "cal-icon-%02d-%02d.png" % (details['month'],
+ details['day'])
+ iconPath = os.path.join(iconDir, language, iconName)
+ if not os.path.exists(iconPath):
+ # Try the generic (numeric) version
+ iconPath = os.path.join(iconDir, 'generic', iconName)
- msgAlt = MIMEMultipart("alternative")
- msg.attach(msgAlt)
+ with translationTo(language):
+ msg = MIMEMultipart()
+ msg["From"] = fromAddress
+ msg["Reply-To"] = replyToAddress
+ msg["To"] = toAddress
+ msg["Date"] = rfc822date()
+ msgId = messageid()
+ msg["Message-ID"] = msgId
- # plain text version
- if calendar.propertyValue("METHOD") == "CANCEL":
- msg["Subject"] = "Event cancelled"
- plainText = u"An event has been cancelled. Click the link below.\n"
- else:
- msg["Subject"] = "Event invitation: %s" % (title,)
- plainText = u"You've been invited to the following event: %s To accept or decline this invitation, click the link below.\n" % (summary,)
+ canceled = (calendar.propertyValue("METHOD") == "CANCEL")
+ if canceled:
+ formatString = _("Event canceled: %(summary)s")
+ elif newInvitation:
+ formatString = _("Event invitation: %(summary)s")
+ else:
+ formatString = _("Event update: %(summary)s")
- msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
- msgAlt.attach(msgPlain)
+ details['subject'] = msg['Subject'] = formatString % {
+ 'summary' : details['summary']
+ }
- # html version
- msgHtmlRelated = MIMEMultipart("related", type="text/html")
- msgAlt.attach(msgHtmlRelated)
- htmlText = u"""
-<html><body><div>
-<img src="cid:ical.jpg">
-%s
-</div></body></html>
-""" % plainText
+ msgAlt = MIMEMultipart("alternative")
+ msg.attach(msgAlt)
+
+ # Get localized labels
+ if canceled:
+ details['inviteLabel'] = _("Event Canceled")
+ else:
+ if newInvitation:
+ details['inviteLabel'] = _("Event Invitation")
+ else:
+ details['inviteLabel'] = _("Event Update")
+
+ details['dateLabel'] = _("Date")
+ details['timeLabel'] = _("Time")
+ details['durationLabel'] = _("Duration")
+ details['recurrenceLabel'] = _("Occurs")
+ details['descLabel'] = _("Description")
+ details['orgLabel'] = _("Organizer")
+ details['attLabel'] = _("Attendees")
+ details['locLabel'] = _("Location")
+
+
+ plainAttendeeList = []
+ for cn, mailto in attendees:
+ if cn:
+ plainAttendeeList.append(cn if not mailto else
+ "%s <%s>" % (cn, mailto))
+ elif mailto:
+ plainAttendeeList.append("<%s>" % (mailto,))
+
+ details['plainAttendees'] = ", ".join(plainAttendeeList)
+
+ details['plainOrganizer'] = (orgCN if not orgEmail else
+ "%s <%s>" % (orgCN, orgEmail))
+
+ # plain text version
+ if canceled:
+ plainTemplate = u"""%(subject)s
+
+%(orgLabel)s: %(plainOrganizer)s
+%(dateLabel)s: %(dateInfo)s %(recurrenceInfo)s
+%(timeLabel)s: %(timeInfo)s %(durationInfo)s
+"""
+ else:
+ plainTemplate = u"""%(subject)s
+
+%(orgLabel)s: %(plainOrganizer)s
+%(locLabel)s: %(location)s
+%(dateLabel)s: %(dateInfo)s %(recurrenceInfo)s
+%(timeLabel)s: %(timeInfo)s %(durationInfo)s
+%(descLabel)s: %(description)s
+%(attLabel)s: %(plainAttendees)s
+"""
+
+ plainText = plainTemplate % details
+
+ msgPlain = MIMEText(plainText.encode("UTF-8"), "plain", "UTF-8")
+ msgAlt.attach(msgPlain)
+
+ # html version
+ msgHtmlRelated = MIMEMultipart("related", type="text/html")
+ msgAlt.attach(msgHtmlRelated)
+
+
+ htmlAttendees = []
+ for cn, mailto in attendees:
+ if mailto:
+ htmlAttendees.append('<a href="mailto:%s">%s</a>' %
+ (mailto, cn))
+ else:
+ htmlAttendees.append(cn)
+
+ details['htmlAttendees'] = ", ".join(htmlAttendees)
+
+ if orgEmail:
+ details['htmlOrganizer'] = '<a href="mailto:%s">%s</a>' % (
+ orgEmail, orgCN)
+ else:
+ details['htmlOrganizer'] = orgCN
+
+ details['iconName'] = iconName
+
+ templateDir = config.Scheduling["iMIP"]["MailTemplatesDirectory"].rstrip("/")
+ templateName = "cancel.html" if canceled else "invite.html"
+ templatePath = os.path.join(templateDir, templateName)
+
+ if not os.path.exists(templatePath):
+ # Fall back to built-in simple templates:
+ if canceled:
+
+ htmlTemplate = u"""<html>
+ <body><div>
+
+ <h1>%(subject)s</h1>
+ <p>
+ <h3>%(orgLabel)s:</h3> %(htmlOrganizer)s
+ </p>
+ <p>
+ <h3>%(dateLabel)s:</h3> %(dateInfo)s %(recurrenceInfo)s
+ </p>
+ <p>
+ <h3>%(timeLabel)s:</h3> %(timeInfo)s %(durationInfo)s
+ </p>
+
+ """
+
+ else:
+
+ htmlTemplate = u"""<html>
+ <body><div>
+ <p>%(inviteLabel)s</p>
+
+ <h1>%(summary)s</h1>
+ <p>
+ <h3>%(orgLabel)s:</h3> %(htmlOrganizer)s
+ </p>
+ <p>
+ <h3>%(locLabel)s:</h3> %(location)s
+ </p>
+ <p>
+ <h3>%(dateLabel)s:</h3> %(dateInfo)s %(recurrenceInfo)s
+ </p>
+ <p>
+ <h3>%(timeLabel)s:</h3> %(timeInfo)s %(durationInfo)s
+ </p>
+ <p>
+ <h3>%(descLabel)s:</h3> %(description)s
+ </p>
+ <p>
+ <h3>%(attLabel)s:</h3> %(htmlAttendees)s
+ </p>
+
+ """
+ else: # HTML template file exists
+
+ with open(templatePath) as templateFile:
+ htmlTemplate = templateFile.read()
+
+ htmlText = htmlTemplate % details
+
msgHtml = MIMEText(htmlText.encode("UTF-8"), "html", "UTF-8")
msgHtmlRelated.attach(msgHtml)
# an image for html version
- imageName = "ical.jpg"
- imageFile = open(os.path.join(os.path.dirname(__file__),
- "images", "mail", imageName))
- msgImage = MIMEImage(imageFile.read(),
- _subtype='jpeg;x-apple-mail-type=stationery;name="%s"' %
- (imageName,))
- imageFile.close()
- msgImage.add_header("Content-ID", "<%s>" % (imageName,))
- msgImage.add_header("Content-Disposition", "inline;filename=%s" %
- (imageName,))
- msgHtmlRelated.attach(msgImage)
+ if os.path.exists(iconPath) and htmlTemplate.find("cid:%(iconName)s") != -1:
+ with open(iconPath) as iconFile:
+ msgIcon = MIMEImage(iconFile.read(),
+ _subtype='png;x-apple-mail-type=stationery;name="%s"' %
+ (iconName,))
+
+ msgIcon.add_header("Content-ID", "<%s>" % (iconName,))
+ msgIcon.add_header("Content-Disposition", "inline;filename=%s" %
+ (iconName,))
+ msgHtmlRelated.attach(msgIcon)
+
# the icalendar attachment
self.log_debug("Mail gateway sending calendar body: %s" % (str(calendar)))
msgIcal = MIMEText(str(calendar), "calendar", "UTF-8")
method = calendar.propertyValue("METHOD").lower()
msgIcal.set_param("method", method)
+ msgIcal.add_header("Content-ID", "<invitation.ics>")
msgIcal.add_header("Content-Disposition",
"inline;filename=invitation.ics")
msg.attach(msgIcal)
@@ -769,135 +975,55 @@
return msgId, msg.as_string()
- def _generateCalendarSummary(self, calendar, organizer):
+ def getEventDetails(self, calendar, language='en'):
# Get the most appropriate component
component = calendar.masterComponent()
if component is None:
component = calendar.mainComponent(True)
- organizerProp = component.getOrganizerProperty()
- if "CN" in organizerProp.params():
- organizer = "%s <%s>" % (organizerProp.params()["CN"][0],
- organizer,)
+ results = { }
- if calendar.propertyValue("METHOD") == "CANCEL":
- dtinfo = ""
- else:
- dtinfo = self._getDateTimeInfo(component)
+ dtStart = component.propertyNativeValue("DTSTART")
+ results['month'] = dtStart.month
+ results['day'] = dtStart.day
summary = component.propertyValue("SUMMARY")
if summary is None:
summary = ""
+ results['summary'] = summary
description = component.propertyValue("DESCRIPTION")
if description is None:
description = ""
+ results['description'] = description
- return summary, """
+ location = component.propertyValue("LOCATION")
+ if location is None:
+ location = ""
+ results['location'] = location
-Summary: %s
-Organizer: %s
-%sDescription: %s
+ with translationTo(language) as trans:
+ results['dateInfo'] = trans.date(component)
+ results['timeInfo'], duration = trans.time(component)
+ results['durationInfo'] = "(%s)" % (duration,) if duration else ""
-""" % (summary, organizer, dtinfo, description,)
-
- def _getDateTimeInfo(self, component):
-
- dtstart = component.propertyNativeValue("DTSTART")
- tzid_start = component.getProperty("DTSTART").params().get("TZID", "UTC")
-
- dtend = component.propertyNativeValue("DTEND")
- if dtend:
- tzid_end = component.getProperty("DTEND").params().get("TZID", "UTC")
- duration = dtend - dtstart
- else:
- duration = component.propertyNativeValue("DURATION")
- if duration:
- dtend = dtstart + duration
- tzid_end = tzid_start
+ for propertyName in ("RRULE", "RDATE", "EXRULE", "EXDATE",
+ "RECURRENCE-ID"):
+ if component.hasProperty(propertyName):
+ results['recurrenceInfo'] = _("(Repeating)")
+ break
else:
- if isinstance(dtstart, datetime.date):
- dtend = None
- duration = datetime.timedelta(days=1)
- else:
- dtend = dtstart + datetime.timedelta(days=1)
- dtend.hour = dtend.minute = dtend.second = 0
- duration = dtend - dtstart
- result = "Starts: %s\n" % (self._getDateTimeText(dtstart, tzid_start),)
- if dtend is not None:
- result += "Ends: %s\n" % (self._getDateTimeText(dtend, tzid_end),)
- result += "Duration: %s\n" % (self._getDurationText(duration),)
+ results['recurrenceInfo'] = ""
- if not isinstance(dtstart, datetime.datetime):
- result += "All Day\n"
+ return results
- for property_name in ("RRULE", "RDATE", "EXRULE", "EXDATE", "RECURRENCE-ID",):
- if component.hasProperty(property_name):
- result += "Recurring\n"
- break
- return result
- def _getDateTimeText(self, dtvalue, tzid):
- if isinstance(dtvalue, datetime.datetime):
- timeformat = "%A, %B %e, %Y %I:%M %p"
- elif isinstance(dtvalue, datetime.date):
- timeformat = "%A, %B %e, %Y"
- tzid = ""
- if tzid:
- tzid = " (%s)" % (tzid,)
- return "%s%s" % (dtvalue.strftime(timeformat), tzid,)
- def _getDurationText(self, duration):
- result = ""
- if duration.days > 0:
- result += "%d %s" % (
- duration.days,
- self._pluralize(duration.days, "day", "days")
- )
-
- hours = duration.seconds / 3600
- minutes = divmod(duration.seconds / 60, 60)[1]
- seconds = divmod(duration.seconds, 60)[1]
-
- if hours > 0:
- if result:
- result += ", "
- result += "%d %s" % (
- hours,
- self._pluralize(hours, "hour", "hours")
- )
-
- if minutes > 0:
- if result:
- result += ", "
- result += "%d %s" % (
- minutes,
- self._pluralize(minutes, "minute", "minutes")
- )
-
- if seconds > 0:
- if result:
- result += ", "
- result += "%d %s" % (
- seconds,
- self._pluralize(seconds, "second", "seconds")
- )
-
- return result
-
- def _pluralize(self, number, unit1, unitS):
- return unit1 if number == 1 else unitS
-
-
-
-
-
-
#
# POP3
#
@@ -972,6 +1098,7 @@
from twisted.internet import reactor
self.reactor = reactor
self.nextPoll = None
+ self.noisy = False
def retry(self, connector=None):
# TODO: if connector is None:
@@ -1147,6 +1274,7 @@
if reactor is None:
from twisted.internet import reactor
self.reactor = reactor
+ self.noisy = False
def handleMessage(self, message):
Deleted: CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Copied: CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.mo)
===================================================================
(Binary files differ)
Deleted: CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po 2008-11-06 01:01:07 UTC (rev 3323)
+++ CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po 2008-11-06 01:45:12 UTC (rev 3325)
@@ -1,249 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2008-10-24 15:03+PDT\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=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-
-#: localization.py:171
-msgid "All day"
-msgstr ""
-
-#: localization.py:177
-msgid "%(startTime)s to %(endTime)s"
-msgstr ""
-
-#: localization.py:191
-msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
-msgstr ""
-
-#: localization.py:207
-msgid "AM"
-msgstr ""
-
-#: localization.py:207
-msgid "PM"
-msgstr ""
-
-#: localization.py:213
-msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
-msgstr ""
-
-#: localization.py:236
-msgid "Monday"
-msgstr ""
-
-#: localization.py:237
-msgid "Tuesday"
-msgstr ""
-
-#: localization.py:238
-msgid "Wednesday"
-msgstr ""
-
-#: localization.py:239
-msgid "Thursday"
-msgstr ""
-
-#: localization.py:240
-msgid "Friday"
-msgstr ""
-
-#: localization.py:241
-msgid "Saturday"
-msgstr ""
-
-#: localization.py:242
-msgid "Sunday"
-msgstr ""
-
-#: localization.py:246
-msgid "Mon"
-msgstr ""
-
-#: localization.py:247
-msgid "Tue"
-msgstr ""
-
-#: localization.py:248
-msgid "Wed"
-msgstr ""
-
-#: localization.py:249
-msgid "Thu"
-msgstr ""
-
-#: localization.py:250
-msgid "Fri"
-msgstr ""
-
-#: localization.py:251
-msgid "Sun"
-msgstr ""
-
-#: localization.py:252
-msgid "Sat"
-msgstr ""
-
-#: localization.py:257
-msgid "January"
-msgstr ""
-
-#: localization.py:258
-msgid "February"
-msgstr ""
-
-#: localization.py:259
-msgid "March"
-msgstr ""
-
-#: localization.py:260
-msgid "April"
-msgstr ""
-
-#: localization.py:261 localization.py:277
-msgid "May"
-msgstr ""
-
-#: localization.py:262
-msgid "June"
-msgstr ""
-
-#: localization.py:263
-msgid "July"
-msgstr ""
-
-#: localization.py:264
-msgid "August"
-msgstr ""
-
-#: localization.py:265
-msgid "September"
-msgstr ""
-
-#: localization.py:266
-msgid "October"
-msgstr ""
-
-#: localization.py:267
-msgid "November"
-msgstr ""
-
-#: localization.py:268
-msgid "December"
-msgstr ""
-
-#: localization.py:273
-msgid "Jan"
-msgstr ""
-
-#: localization.py:274
-msgid "Feb"
-msgstr ""
-
-#: localization.py:275
-msgid "Mar"
-msgstr ""
-
-#: localization.py:276
-msgid "Apr"
-msgstr ""
-
-#: localization.py:278
-msgid "Jun"
-msgstr ""
-
-#: localization.py:279
-msgid "Jul"
-msgstr ""
-
-#: localization.py:280
-msgid "Aug"
-msgstr ""
-
-#: localization.py:281
-msgid "Sep"
-msgstr ""
-
-#: localization.py:282
-msgid "Oct"
-msgstr ""
-
-#: localization.py:283
-msgid "Nov"
-msgstr ""
-
-#: localization.py:284
-msgid "Dec"
-msgstr ""
-
-#: mail.py:726 mail.py:755 mail.py:792
-msgid "Event cancelled"
-msgstr ""
-
-#: mail.py:727
-msgid "Event invitation: %(summary)s"
-msgstr ""
-
-#: mail.py:736
-msgid "Event Invitation"
-msgstr ""
-
-#: mail.py:737
-msgid "Date"
-msgstr ""
-
-#: mail.py:738
-msgid "Time"
-msgstr ""
-
-#: mail.py:739
-msgid "Description"
-msgstr ""
-
-#: mail.py:740
-msgid "Organizer"
-msgstr ""
-
-#: mail.py:741
-msgid "Attendees"
-msgstr ""
-
-#: mail.py:742
-msgid "Location"
-msgstr ""
-
-
-msgid "1 day"
-msgstr
-
-msgid "%(dayCount)d days"
-msgstr
-
-msgid "1 hour"
-
-msgid "%(hourCount)d hours"
-msgstr
-
-msgid "1 minute"
-msgstr
-
-msgid "%(minuteCount)d minutes"
-msgstr
-
-msgid "1 second"
-msgstr
-
-msgid "%(secondCount)d seconds"
-msgstr
-
Copied: CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/locales/en/LC_MESSAGES/calendarserver.po 2008-11-06 01:45:12 UTC (rev 3325)
@@ -0,0 +1,249 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2008-10-24 15:03+PDT\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+
+
+#: localization.py:171
+msgid "All day"
+msgstr ""
+
+#: localization.py:177
+msgid "%(startTime)s to %(endTime)s"
+msgstr ""
+
+#: localization.py:191
+msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
+msgstr ""
+
+#: localization.py:207
+msgid "AM"
+msgstr ""
+
+#: localization.py:207
+msgid "PM"
+msgstr ""
+
+#: localization.py:213
+msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
+msgstr ""
+
+#: localization.py:236
+msgid "Monday"
+msgstr ""
+
+#: localization.py:237
+msgid "Tuesday"
+msgstr ""
+
+#: localization.py:238
+msgid "Wednesday"
+msgstr ""
+
+#: localization.py:239
+msgid "Thursday"
+msgstr ""
+
+#: localization.py:240
+msgid "Friday"
+msgstr ""
+
+#: localization.py:241
+msgid "Saturday"
+msgstr ""
+
+#: localization.py:242
+msgid "Sunday"
+msgstr ""
+
+#: localization.py:246
+msgid "Mon"
+msgstr ""
+
+#: localization.py:247
+msgid "Tue"
+msgstr ""
+
+#: localization.py:248
+msgid "Wed"
+msgstr ""
+
+#: localization.py:249
+msgid "Thu"
+msgstr ""
+
+#: localization.py:250
+msgid "Fri"
+msgstr ""
+
+#: localization.py:251
+msgid "Sun"
+msgstr ""
+
+#: localization.py:252
+msgid "Sat"
+msgstr ""
+
+#: localization.py:257
+msgid "January"
+msgstr ""
+
+#: localization.py:258
+msgid "February"
+msgstr ""
+
+#: localization.py:259
+msgid "March"
+msgstr ""
+
+#: localization.py:260
+msgid "April"
+msgstr ""
+
+#: localization.py:261 localization.py:277
+msgid "May"
+msgstr ""
+
+#: localization.py:262
+msgid "June"
+msgstr ""
+
+#: localization.py:263
+msgid "July"
+msgstr ""
+
+#: localization.py:264
+msgid "August"
+msgstr ""
+
+#: localization.py:265
+msgid "September"
+msgstr ""
+
+#: localization.py:266
+msgid "October"
+msgstr ""
+
+#: localization.py:267
+msgid "November"
+msgstr ""
+
+#: localization.py:268
+msgid "December"
+msgstr ""
+
+#: localization.py:273
+msgid "Jan"
+msgstr ""
+
+#: localization.py:274
+msgid "Feb"
+msgstr ""
+
+#: localization.py:275
+msgid "Mar"
+msgstr ""
+
+#: localization.py:276
+msgid "Apr"
+msgstr ""
+
+#: localization.py:278
+msgid "Jun"
+msgstr ""
+
+#: localization.py:279
+msgid "Jul"
+msgstr ""
+
+#: localization.py:280
+msgid "Aug"
+msgstr ""
+
+#: localization.py:281
+msgid "Sep"
+msgstr ""
+
+#: localization.py:282
+msgid "Oct"
+msgstr ""
+
+#: localization.py:283
+msgid "Nov"
+msgstr ""
+
+#: localization.py:284
+msgid "Dec"
+msgstr ""
+
+#: mail.py:726 mail.py:755 mail.py:792
+msgid "Event cancelled"
+msgstr ""
+
+#: mail.py:727
+msgid "Event invitation: %(summary)s"
+msgstr ""
+
+#: mail.py:736
+msgid "Event Invitation"
+msgstr ""
+
+#: mail.py:737
+msgid "Date"
+msgstr ""
+
+#: mail.py:738
+msgid "Time"
+msgstr ""
+
+#: mail.py:739
+msgid "Description"
+msgstr ""
+
+#: mail.py:740
+msgid "Organizer"
+msgstr ""
+
+#: mail.py:741
+msgid "Attendees"
+msgstr ""
+
+#: mail.py:742
+msgid "Location"
+msgstr ""
+
+
+msgid "1 day"
+msgstr
+
+msgid "%(dayCount)d days"
+msgstr
+
+msgid "1 hour"
+
+msgid "%(hourCount)d hours"
+msgstr
+
+msgid "1 minute"
+msgstr
+
+msgid "%(minuteCount)d minutes"
+msgstr
+
+msgid "1 second"
+msgstr
+
+msgid "%(secondCount)d seconds"
+msgstr
+
Deleted: CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo
===================================================================
(Binary files differ)
Copied: CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.mo)
===================================================================
(Binary files differ)
Deleted: CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po
===================================================================
--- CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po 2008-11-06 01:01:07 UTC (rev 3323)
+++ CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po 2008-11-06 01:45:12 UTC (rev 3325)
@@ -1,250 +0,0 @@
-# Pig Latin Translation
-# Copyright (C) YEAR ORGANIZATION
-# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2008-10-24 15:03+PDT\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=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-
-
-#: localization.py:171
-msgid "All day"
-msgstr "Allway ayday"
-
-#: localization.py:177
-msgid "%(startTime)s to %(endTime)s"
-msgstr "%(startTime)s otay %(endTime)s"
-
-#: localization.py:191
-msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
-msgstr "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
-
-#: localization.py:207
-msgid "AM"
-msgstr "AMWAY"
-
-#: localization.py:207
-msgid "PM"
-msgstr "PMAY"
-
-#: localization.py:213
-msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
-msgstr "%(hour24Number)02d:%(minuteNumber)02d"
-
-#: localization.py:236
-msgid "Monday"
-msgstr "Ondaymay"
-
-#: localization.py:237
-msgid "Tuesday"
-msgstr "Uesdaytay"
-
-#: localization.py:238
-msgid "Wednesday"
-msgstr "Ednesdayway"
-
-#: localization.py:239
-msgid "Thursday"
-msgstr "Ursdaythay"
-
-#: localization.py:240
-msgid "Friday"
-msgstr "Idayfray"
-
-#: localization.py:241
-msgid "Saturday"
-msgstr "Aturdaysay"
-
-#: localization.py:242
-msgid "Sunday"
-msgstr "Undaysay"
-
-#: localization.py:246
-msgid "Mon"
-msgstr ""
-
-#: localization.py:247
-msgid "Tue"
-msgstr ""
-
-#: localization.py:248
-msgid "Wed"
-msgstr ""
-
-#: localization.py:249
-msgid "Thu"
-msgstr ""
-
-#: localization.py:250
-msgid "Fri"
-msgstr ""
-
-#: localization.py:251
-msgid "Sun"
-msgstr ""
-
-#: localization.py:252
-msgid "Sat"
-msgstr ""
-
-#: localization.py:257
-msgid "January"
-msgstr "Anuaryjay"
-
-#: localization.py:258
-msgid "February"
-msgstr "Ebruaryfay"
-
-#: localization.py:259
-msgid "March"
-msgstr "Archmay"
-
-#: localization.py:260
-msgid "April"
-msgstr "Aprilway"
-
-#: localization.py:261 localization.py:277
-msgid "May"
-msgstr "Aymay"
-
-#: localization.py:262
-msgid "June"
-msgstr "Unejay"
-
-#: localization.py:263
-msgid "July"
-msgstr "Ulyjay"
-
-#: localization.py:264
-msgid "August"
-msgstr "Augustway"
-
-#: localization.py:265
-msgid "September"
-msgstr "Eptembersay"
-
-#: localization.py:266
-msgid "October"
-msgstr "Octoberway"
-
-#: localization.py:267
-msgid "November"
-msgstr "Ovembernay"
-
-#: localization.py:268
-msgid "December"
-msgstr "Ecemberday"
-
-#: localization.py:273
-msgid "Jan"
-msgstr ""
-
-#: localization.py:274
-msgid "Feb"
-msgstr ""
-
-#: localization.py:275
-msgid "Mar"
-msgstr ""
-
-#: localization.py:276
-msgid "Apr"
-msgstr ""
-
-#: localization.py:278
-msgid "Jun"
-msgstr ""
-
-#: localization.py:279
-msgid "Jul"
-msgstr ""
-
-#: localization.py:280
-msgid "Aug"
-msgstr ""
-
-#: localization.py:281
-msgid "Sep"
-msgstr ""
-
-#: localization.py:282
-msgid "Oct"
-msgstr ""
-
-#: localization.py:283
-msgid "Nov"
-msgstr ""
-
-#: localization.py:284
-msgid "Dec"
-msgstr ""
-
-#: mail.py:726 mail.py:755 mail.py:792
-msgid "Event cancelled"
-msgstr "Eventway ancelledcay"
-
-#: mail.py:727
-msgid "Event invitation: %(summary)s"
-msgstr "Eventway invitationway: %(summary)s"
-
-#: mail.py:736
-msgid "Event Invitation"
-msgstr "Eventway invitationway"
-
-#: mail.py:737
-msgid "Date"
-msgstr "Ateday"
-
-#: mail.py:738
-msgid "Time"
-msgstr "Imetay"
-
-#: mail.py:739
-msgid "Description"
-msgstr "Escriptionday"
-
-#: mail.py:740
-msgid "Organizer"
-msgstr "Organizerway"
-
-#: mail.py:741
-msgid "Attendees"
-msgstr "Attendeesway"
-
-#: mail.py:742
-msgid "Location"
-msgstr "Ocationlay"
-
-
-msgid "1 day"
-msgstr "1 ayday"
-
-msgid "%(dayCount)d days"
-msgstr "%(dayCount)d aysday"
-
-msgid "1 hour"
-msgstr "1 ourhay"
-
-msgid "%(hourCount)d hours"
-msgstr "%(hourCount)d ourshay"
-
-msgid "1 minute"
-msgstr "1 inutemay"
-
-msgid "%(minuteCount)d minutes"
-msgstr "%(minuteCount)d inutesmay"
-
-msgid "1 second"
-msgstr "1 econdsay"
-
-msgid "%(secondCount)d seconds"
-msgstr "%(secondCount)d econdsay"
-
Copied: CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/data/locales/pig/LC_MESSAGES/calendarserver.po 2008-11-06 01:45:12 UTC (rev 3325)
@@ -0,0 +1,250 @@
+# Pig Latin Translation
+# Copyright (C) YEAR ORGANIZATION
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: 2008-10-24 15:03+PDT\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+
+
+#: localization.py:171
+msgid "All day"
+msgstr "Allway ayday"
+
+#: localization.py:177
+msgid "%(startTime)s to %(endTime)s"
+msgstr "%(startTime)s otay %(endTime)s"
+
+#: localization.py:191
+msgid "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
+msgstr "%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d"
+
+#: localization.py:207
+msgid "AM"
+msgstr "AMWAY"
+
+#: localization.py:207
+msgid "PM"
+msgstr "PMAY"
+
+#: localization.py:213
+msgid "%(hour12Number)d:%(minuteNumber)02d %(ampm)s"
+msgstr "%(hour24Number)02d:%(minuteNumber)02d"
+
+#: localization.py:236
+msgid "Monday"
+msgstr "Ondaymay"
+
+#: localization.py:237
+msgid "Tuesday"
+msgstr "Uesdaytay"
+
+#: localization.py:238
+msgid "Wednesday"
+msgstr "Ednesdayway"
+
+#: localization.py:239
+msgid "Thursday"
+msgstr "Ursdaythay"
+
+#: localization.py:240
+msgid "Friday"
+msgstr "Idayfray"
+
+#: localization.py:241
+msgid "Saturday"
+msgstr "Aturdaysay"
+
+#: localization.py:242
+msgid "Sunday"
+msgstr "Undaysay"
+
+#: localization.py:246
+msgid "Mon"
+msgstr ""
+
+#: localization.py:247
+msgid "Tue"
+msgstr ""
+
+#: localization.py:248
+msgid "Wed"
+msgstr ""
+
+#: localization.py:249
+msgid "Thu"
+msgstr ""
+
+#: localization.py:250
+msgid "Fri"
+msgstr ""
+
+#: localization.py:251
+msgid "Sun"
+msgstr ""
+
+#: localization.py:252
+msgid "Sat"
+msgstr ""
+
+#: localization.py:257
+msgid "January"
+msgstr "Anuaryjay"
+
+#: localization.py:258
+msgid "February"
+msgstr "Ebruaryfay"
+
+#: localization.py:259
+msgid "March"
+msgstr "Archmay"
+
+#: localization.py:260
+msgid "April"
+msgstr "Aprilway"
+
+#: localization.py:261 localization.py:277
+msgid "May"
+msgstr "Aymay"
+
+#: localization.py:262
+msgid "June"
+msgstr "Unejay"
+
+#: localization.py:263
+msgid "July"
+msgstr "Ulyjay"
+
+#: localization.py:264
+msgid "August"
+msgstr "Augustway"
+
+#: localization.py:265
+msgid "September"
+msgstr "Eptembersay"
+
+#: localization.py:266
+msgid "October"
+msgstr "Octoberway"
+
+#: localization.py:267
+msgid "November"
+msgstr "Ovembernay"
+
+#: localization.py:268
+msgid "December"
+msgstr "Ecemberday"
+
+#: localization.py:273
+msgid "Jan"
+msgstr ""
+
+#: localization.py:274
+msgid "Feb"
+msgstr ""
+
+#: localization.py:275
+msgid "Mar"
+msgstr ""
+
+#: localization.py:276
+msgid "Apr"
+msgstr ""
+
+#: localization.py:278
+msgid "Jun"
+msgstr ""
+
+#: localization.py:279
+msgid "Jul"
+msgstr ""
+
+#: localization.py:280
+msgid "Aug"
+msgstr ""
+
+#: localization.py:281
+msgid "Sep"
+msgstr ""
+
+#: localization.py:282
+msgid "Oct"
+msgstr ""
+
+#: localization.py:283
+msgid "Nov"
+msgstr ""
+
+#: localization.py:284
+msgid "Dec"
+msgstr ""
+
+#: mail.py:726 mail.py:755 mail.py:792
+msgid "Event cancelled"
+msgstr "Eventway ancelledcay"
+
+#: mail.py:727
+msgid "Event invitation: %(summary)s"
+msgstr "Eventway invitationway: %(summary)s"
+
+#: mail.py:736
+msgid "Event Invitation"
+msgstr "Eventway invitationway"
+
+#: mail.py:737
+msgid "Date"
+msgstr "Ateday"
+
+#: mail.py:738
+msgid "Time"
+msgstr "Imetay"
+
+#: mail.py:739
+msgid "Description"
+msgstr "Escriptionday"
+
+#: mail.py:740
+msgid "Organizer"
+msgstr "Organizerway"
+
+#: mail.py:741
+msgid "Attendees"
+msgstr "Attendeesway"
+
+#: mail.py:742
+msgid "Location"
+msgstr "Ocationlay"
+
+
+msgid "1 day"
+msgstr "1 ayday"
+
+msgid "%(dayCount)d days"
+msgstr "%(dayCount)d aysday"
+
+msgid "1 hour"
+msgstr "1 ourhay"
+
+msgid "%(hourCount)d hours"
+msgstr "%(hourCount)d ourshay"
+
+msgid "1 minute"
+msgstr "1 inutemay"
+
+msgid "%(minuteCount)d minutes"
+msgstr "%(minuteCount)d inutesmay"
+
+msgid "1 second"
+msgstr "1 econdsay"
+
+msgid "%(secondCount)d seconds"
+msgstr "%(secondCount)d econdsay"
+
Copied: CalendarServer/trunk/twistedcaldav/test/test_localization.py (from rev 3323, CalendarServer/branches/users/sagen/localization-3308/twistedcaldav/test/test_localization.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_localization.py (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_localization.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -0,0 +1,137 @@
+##
+# Copyright (c) 2008 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from __future__ import with_statement
+
+from twisted.trial.unittest import TestCase
+from twistedcaldav.localization import translationTo
+from twistedcaldav.ical import Component
+from datetime import datetime, time
+import os
+
+def getComp(str):
+ calendar = Component.fromString(str)
+ comp = calendar.masterComponent()
+ if comp is None:
+ comp = calendar.mainComponent(True)
+ return comp
+
+data = (
+ ('1-hour-long-morning', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T091500\r\nDTEND;TZID=US/Pacific:20081025T101501\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at ex
ample.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+ ('2-hour-long-evening', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T131500\r\nDTEND;TZID=US/Pacific:20081025T151502\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at ex
ample.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+ ('3-hour-long-cross-noon', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T110500\r\nDTEND;TZID=US/Pacific:20081025T141500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser
@example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+ ('all-day', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\
r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+ ('instantaneous', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:20081025T131500\r\nDTEND;TZID=US/Pacific:20081025T131500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.
com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+ ('cross-timezone', getComp('BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nMETHOD:REQUEST\r\nPRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN\r\nBEGIN:VTIMEZONE\r\nTZID:US/Pacific\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:PST\r\nTZOFFSETFROM:-0700\r\nTZOFFSETTO:-0800\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:PDT\r\nTZOFFSETFROM:-0800\r\nTZOFFSETTO:-0700\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VTIMEZONE\r\nTZID:US/Eastern\r\nBEGIN:STANDARD\r\nDTSTART:20071104T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\nTZNAME:EST\r\nTZOFFSETFROM:-0400\r\nTZOFFSETTO:-0500\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nDTSTART:20070311T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\nTZNAME:EDT\r\nTZOFFSETFROM:-0500\r\nTZOFFSETTO:-0400\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:C7C037CC-1485-461B-8866-777C662C5930\r\nDTSTART;TZID=US/Pacific:
20081025T110500\r\nDTEND;TZID=US/Eastern:20081025T181500\r\nATTENDEE;CN=test at systemcall.com;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSV\r\n P=TRUE:mailto:test at systemcall.com\r\nATTENDEE;CN=Test User;CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:mailto:testuser@\r\n example.com\r\nCREATED:20081024T181749Z\r\nDTSTAMP:20081024T183142Z\r\nORGANIZER;CN=Test User:mailto:testuser at example.com\r\nSEQUENCE:5\r\nSUMMARY:testing\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n')),
+
+
+)
+
+localeDir = os.path.join(os.path.dirname(__file__), "data", "locales")
+
+class LocalizationTests(TestCase):
+
+ def test_BasicStringLocalization(self):
+
+ with translationTo('pig', localeDir=localeDir):
+
+ self.assertEquals(_("All day"), "Allway ayday")
+
+ self.assertEquals(_("%(startTime)s to %(endTime)s") %
+ { 'startTime' : 'a', 'endTime' : 'b' },
+ "a otay b"
+ )
+
+ def test_TimeFormattingAMPM(self):
+
+ with translationTo('en', localeDir=localeDir) as t:
+
+ self.assertEquals(t.dtTime(time(0,0)), "12:00 AM")
+ self.assertEquals(t.dtTime(time(12,0)), "12:00 PM")
+ self.assertEquals(t.dtTime(time(23,59)), "11:59 PM")
+ self.assertEquals(t.dtTime(time(6,5)), "6:05 AM")
+ self.assertEquals(t.dtTime(time(16,5)), "4:05 PM")
+
+ def test_TimeFormatting24Hour(self):
+
+ with translationTo('pig', localeDir=localeDir) as t:
+
+ self.assertEquals(t.dtTime(time(0,0)), "00:00")
+ self.assertEquals(t.dtTime(time(12,0)), "12:00")
+ self.assertEquals(t.dtTime(time(23,59)), "23:59")
+ self.assertEquals(t.dtTime(time(6,5)), "06:05")
+ self.assertEquals(t.dtTime(time(16,5)), "16:05")
+
+ def test_CalendarFormatting(self):
+
+ with translationTo('en', localeDir=localeDir) as t:
+
+ comp = data[0][1]
+ self.assertEquals(t.date(comp), "Saturday, October 25, 2008")
+ self.assertEquals(t.time(comp),
+ (u'9:15 AM to 10:15 AM PDT', u'1 hour 1 second'))
+
+ comp = data[1][1]
+ self.assertEquals(t.time(comp),
+ (u'1:15 PM to 3:15 PM PDT', u'2 hours 2 seconds'))
+
+ comp = data[2][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 AM to 2:15 PM PDT', u'3 hours 10 minutes'))
+
+ comp = data[3][1]
+ self.assertEquals(t.time(comp),
+ ("", u'All day'))
+
+ comp = data[4][1]
+ self.assertEquals(t.time(comp),
+ (u'1:15 PM PDT', ""))
+
+ comp = data[5][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 AM PDT to 6:15 PM EDT', u'4 hours 10 minutes'))
+
+ with translationTo('pig', localeDir=localeDir) as t:
+
+ comp = data[0][1]
+ self.assertEquals(t.date(comp), 'Aturdaysay, Octoberway 25, 2008')
+ self.assertEquals(t.time(comp),
+ (u'09:15 otay 10:15 PDT', u'1 ourhay 1 econdsay'))
+
+ comp = data[1][1]
+ self.assertEquals(t.time(comp),
+ (u'13:15 otay 15:15 PDT', u'2 ourshay 2 econdsay'))
+
+ comp = data[2][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 otay 14:15 PDT', u'3 ourshay 10 inutesmay'))
+
+ comp = data[3][1]
+ self.assertEquals(t.time(comp),
+ ("", u'Allway ayday'))
+
+ comp = data[4][1]
+ self.assertEquals(t.time(comp),
+ (u'13:15 PDT', ""))
+
+ comp = data[5][1]
+ self.assertEquals(t.time(comp),
+ (u'11:05 PDT otay 18:15 EDT', u'4 ourshay 10 inutesmay'))
Modified: CalendarServer/trunk/twistedcaldav/test/test_mail.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_mail.py 2008-11-06 01:28:37 UTC (rev 3324)
+++ CalendarServer/trunk/twistedcaldav/test/test_mail.py 2008-11-06 01:45:12 UTC (rev 3325)
@@ -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/20081105/6529df62/attachment-0001.html>
More information about the calendarserver-changes
mailing list