[CalendarServer-changes] [3327] CalendarServer/trunk/twistedcaldav/localization.py

source_changes at macosforge.org source_changes at macosforge.org
Wed Nov 5 19:32:34 PST 2008


Revision: 3327
          http://trac.macosforge.org/projects/calendarserver/changeset/3327
Author:   sagen at apple.com
Date:     2008-11-05 19:32:34 -0800 (Wed, 05 Nov 2008)
Log Message:
-----------
Hmm, this didn't get added

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/localization.py

Added: CalendarServer/trunk/twistedcaldav/localization.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/localization.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/localization.py	2008-11-06 03:32:34 UTC (rev 3327)
@@ -0,0 +1,332 @@
+##
+# Copyright (c) 2005-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.
+##
+
+"""
+Localization module
+
+How to use:
+
+    from __future__ import with_statement
+    from localization import translationTo
+
+    with translationTo('de'):
+        print _("Hello")
+        print _("The event will last %(days)d days") % { 'days' : 4 }
+
+    ... Hallo
+    ... Die Veranstaltung dauert 4 Tage
+
+Before you can actually get translated text, you need to:
+
+    1) Choose a "domain" for your code, such as 'calendarserver'
+    2) Run pygettext.py on your source to generate a <domain>.pot file.
+       pygettext.py scans the source for _( ) and copies those strings to the
+       .pot.
+    3) For each language, copy the .pot file to .po and give it to the person
+       who is doing the translation for editing
+    4) Run msgfmt.py on the translated .po to generate a binary .mo
+    5) Put the .mo into locales/<lang>/LC_MESSAGES/<domain>.mo
+
+The German .po file for the above example would look like:
+
+    msgid "Hello"
+    msgstr "Hallo"
+
+    msgid "The event will last %(days)d days"
+    msgstr "Die Veranstaltung dauert %(days)d Tage"
+
+The transationTo class automatically binds '_' to the appropriate translation
+function for the duration of the "with" context.  It's smart enough to allow
+nesting of "with" contexts, as in:
+
+    with translationTo('de'):
+        print _("Hello") # in German
+
+        with translationTo('fr'):
+            print _("Hello") # in French
+
+        print _("Hello") # in German
+
+If a translation file cannot be found for the specified language, it will fall
+back to 'en'.  If 'en' can't be found, gettext will raise IOError.
+
+If you use the with/as form, you will get an object that implements some
+helper methods for date formatting:
+
+    with translationTo('en') as trans:
+        print trans.dtDate(datetime.today())
+
+    ... Thursday, October 23, 2008
+
+    with translationTo('fr') as trans:
+        print trans.dtDate(datetime.today())
+
+    ... Jeudi, Octobre 23, 2008
+
+The .po files contain localizable strings for month and day names, as well as
+date format strings, in case a locale likes these values in a different order
+or with different punctuation.
+
+
+TODO: recurrence
+"""
+
+
+import gettext
+import inspect
+import datetime
+from twistedcaldav.config import config
+
+
+class translationTo(object):
+
+    translations = {}
+
+    def __init__(self, lang, domain='calendarserver', localeDir=None):
+
+        if localeDir is None:
+            localeDir = config.Localization["LocalesDirectory"]
+
+        # Cache gettext translation objects in class.translations
+        key = (lang, domain, localeDir)
+        self.translation = self.translations.get(key, None)
+        if self.translation is None:
+            self.translation = gettext.translation(domain=domain,
+                localedir=localeDir, languages=[lang, 'en'], fallback=True)
+            self.translations[key] = self.translation
+
+    def __enter__(self):
+        # Get the caller's globals so we can rebind their '_' to our translator
+        caller_globals = inspect.stack()[1][0].f_globals
+
+        # Store whatever '_' is already bound to so we can restore it later
+        if caller_globals.has_key('_'):
+            self.prev = caller_globals['_']
+
+        # Rebind '_' to our translator
+        caller_globals['_'] = self.translation.ugettext
+
+        # What we return here is accessible to the caller via the 'as' clause
+        return self
+
+    def __exit__(self, type, value, traceback):
+        # Restore '_' if it previously had a value
+        if hasattr(self, 'prev'):
+            inspect.stack()[1][0].f_globals['_'] = self.prev
+
+        # Don't swallow exceptions
+        return False
+
+    def date(self, component):
+        dtStart = component.propertyNativeValue("DTSTART")
+        return self.dtDate(dtStart)
+
+    def time(self, component):
+        """
+        Examples:
+
+        3:30 PM to 4:30 PM PDT
+        All day
+        3:30 PM PDT
+        3:30 PM PDT to 7:30 PM EDT
+
+        1 day
+        2 days
+        1 day 1 hour
+        1 day 4 hours 18 minutes
+        """
+
+        # Bind to '_' so pygettext.py will pick this up for translation
+        _ = self.translation.ugettext
+
+        tzStart = tzEnd = None
+        dtStart = component.propertyNativeValue("DTSTART")
+        if isinstance(dtStart, datetime.datetime):
+            tzStart = dtStart.tzname()
+        else:
+            return ("", _("All day"))
+
+        # tzStart = component.getProperty("DTSTART").params().get("TZID", "UTC")
+
+        dtEnd = component.propertyNativeValue("DTEND")
+        if dtEnd:
+            if isinstance(dtEnd, datetime.datetime):
+                tzEnd = dtEnd.tzname()
+            # tzEnd = component.getProperty("DTEND").params().get("TZID", "UTC")
+            duration = dtEnd - dtStart
+        else:
+            tzEnd = tzStart
+            duration = component.propertyNativeValue("DURATION")
+            if duration:
+                dtEnd = dtStart + duration
+            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
+
+        if dtStart == dtEnd:
+            return (self.dtTime(dtStart), "")
+
+        return (
+            _("%(startTime)s to %(endTime)s")
+            % {
+                'startTime'      : self.dtTime(dtStart,
+                                    includeTimezone=(tzStart != tzEnd)),
+                'endTime'        : self.dtTime(dtEnd),
+            },
+            self.dtDuration(duration)
+        )
+
+
+    def dtDate(self, val):
+        # Bind to '_' so pygettext.py will pick this up for translation
+        _ = self.translation.ugettext
+
+        return (
+            _("%(dayName)s, %(monthName)s %(dayNumber)d, %(yearNumber)d")
+            % {
+                'dayName'    : _(daysFull[val.weekday()]),
+                'monthName'  : _(monthsFull[val.month]),
+                'dayNumber'  : val.day,
+                'yearNumber' : val.year,
+            }
+        )
+
+    def dtTime(self, val, includeTimezone=True):
+        if not isinstance(val, (datetime.datetime, datetime.time)):
+            return ""
+
+        # Bind to '_' so pygettext.py will pick this up for translation
+        _ = self.translation.ugettext
+
+        ampm = _("AM") if val.hour < 12 else _("PM")
+        hour12 = val.hour % 12
+        if hour12 == 0:
+            hour12 = 12
+
+        result = (
+            _("%(hour12Number)d:%(minuteNumber)02d %(ampm)s")
+            % {
+                'hour24Number' : val.hour, # 0-23
+                'hour12Number' : hour12, # 1-12
+                'minuteNumber' : val.minute, # 0-59
+                'ampm'         : _(ampm),
+            }
+        )
+
+        if includeTimezone and val.tzname():
+            result += " %s" % (val.tzname())
+
+        return result
+
+    def dtDuration(self, val):
+
+        # Bind to '_' so pygettext.py will pick this up for translation
+        _ = self.translation.ugettext
+
+        parts = []
+
+        if val.days == 1:
+            parts.append(_("1 day"))
+        elif val.days > 1:
+            parts.append(_("%(dayCount)d days" %
+                { 'dayCount' : val.days }))
+
+        hours = val.seconds / 3600
+        minutes = divmod(val.seconds / 60, 60)[1]
+        seconds = divmod(val.seconds, 60)[1]
+
+        if hours == 1:
+            parts.append(_("1 hour"))
+        elif hours > 1:
+            parts.append(_("%(hourCount)d hours") %
+                { 'hourCount' : hours })
+
+        if minutes == 1:
+            parts.append(_("1 minute"))
+        elif minutes > 1:
+            parts.append(_("%(minuteCount)d minutes") %
+                { 'minuteCount' : minutes })
+
+        if seconds == 1:
+            parts.append(_("1 second"))
+        elif seconds > 1:
+            parts.append(_("%(secondCount)d seconds") %
+                { 'secondCount' : seconds })
+
+        return " ".join(parts)
+
+
+# The strings below are wrapped in _( ) for the benefit of pygettext.  We don't
+# actually want them translated until they're used.
+
+_ = lambda x: x
+
+daysFull = [
+    _("Monday"),
+    _("Tuesday"),
+    _("Wednesday"),
+    _("Thursday"),
+    _("Friday"),
+    _("Saturday"),
+    _("Sunday"),
+]
+
+daysAbbrev = [
+    _("Mon"),
+    _("Tue"),
+    _("Wed"),
+    _("Thu"),
+    _("Fri"),
+    _("Sun"),
+    _("Sat"),
+]
+
+monthsFull = [
+    "datetime.month is 1-based",
+    _("January"),
+    _("February"),
+    _("March"),
+    _("April"),
+    _("May"),
+    _("June"),
+    _("July"),
+    _("August"),
+    _("September"),
+    _("October"),
+    _("November"),
+    _("December"),
+]
+
+monthsAbbrev = [
+    "datetime.month is 1-based",
+    _("Jan"),
+    _("Feb"),
+    _("Mar"),
+    _("Apr"),
+    _("May"),
+    _("Jun"),
+    _("Jul"),
+    _("Aug"),
+    _("Sep"),
+    _("Oct"),
+    _("Nov"),
+    _("Dec"),
+]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20081105/5b32b4cc/attachment.html>


More information about the calendarserver-changes mailing list