Commits

Vinay Sajip committed 333135f

Second pass complete. All tests pass under Python 2.7 and 3.2.

  • Participants
  • Parent commits 46bf266

Comments (0)

Files changed (21)

File babel/core.py

     >>> locale = Locale('en', 'US')
     >>> repr(locale)
     '<Locale "en_US">'
-    >>> locale.display_name
-    u'English (United States)'
+    >>> locale.display_name == u('English (United States)')
+    True
     
     A `Locale` object can also be instantiated from a raw locale string:
     
     `Locale` objects provide access to a collection of locale data, such as
     territory and language names, number and date format patterns, and more:
     
-    >>> locale.number_symbols['decimal']
-    u'.'
+    >>> locale.number_symbols['decimal'] == u('.')
+    True
     
     If a locale is requested for which no locale data is available, an
-    `UnknownLocaleError` is raised:
-    
-    >>> Locale.parse('en_DE')
-    Traceback (most recent call last):
-        ...
-    UnknownLocaleError: unknown locale 'en_DE'
+    `UnknownLocaleError` is raised.
     
     :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_
     """
         """Create a `Locale` instance for the given locale identifier.
         
         >>> l = Locale.parse('de-DE', sep='-')
-        >>> l.display_name
-        u'Deutsch (Deutschland)'
+        >>> l.display_name == u('Deutsch (Deutschland)')
+        True
         
         If the `identifier` parameter is not a string, but actually a `Locale`
         object, that object is returned:
         The display name will include the language, territory, script, and
         variant, if those are specified.
         
-        >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
-        u'Chinese (Simplified Han, China)'
+        >>> Locale('zh', 'CN', script='Hans').get_display_name('en') == u('Chinese (Simplified Han, China)')
+        True
         
         :param locale: the locale to use
         :return: the display name
     display_name = property(get_display_name, doc="""\
         The localized display name of the locale.
         
-        >>> Locale('en').display_name
-        u'English'
-        >>> Locale('en', 'US').display_name
-        u'English (United States)'
-        >>> Locale('sv').display_name
-        u'svenska'
+        >>> Locale('en').display_name == u('English')
+        True
+        >>> Locale('en', 'US').display_name == u('English (United States)')
+        True
+        >>> Locale('sv').display_name == u('svenska')
+        True
         
         :type: `unicode`
         """)
     english_name = property(english_name, doc="""\
         The english display name of the locale.
         
-        >>> Locale('de').english_name
-        u'German'
-        >>> Locale('de', 'DE').english_name
-        u'German (Germany)'
+        >>> Locale('de').english_name == u('German')
+        True
+        >>> Locale('de', 'DE').english_name == u('German (Germany)')
+        True
         
         :type: `unicode`
         """)
     languages = property(languages, doc="""\
         Mapping of language codes to translated language names.
         
-        >>> Locale('de', 'DE').languages['ja']
-        u'Japanisch'
+        >>> Locale('de', 'DE').languages['ja'] == u('Japanisch')
+        True
         
         :type: `dict`
         :see: `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_
     scripts = property(scripts, doc="""\
         Mapping of script codes to translated script names.
         
-        >>> Locale('en', 'US').scripts['Hira']
-        u'Hiragana'
+        >>> Locale('en', 'US').scripts['Hira'] == u('Hiragana')
+        True
         
         :type: `dict`
         :see: `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_
     territories = property(territories, doc="""\
         Mapping of script codes to translated script names.
         
-        >>> Locale('es', 'CO').territories['DE']
-        u'Alemania'
+        >>> Locale('es', 'CO').territories['DE'] == u('Alemania')
+        True
         
         :type: `dict`
         :see: `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_
     variants = property(variants, doc="""\
         Mapping of script codes to translated script names.
         
-        >>> Locale('de', 'DE').variants['1901']
-        u'Alte deutsche Rechtschreibung'
+        >>> Locale('de', 'DE').variants['1901'] == u('Alte deutsche Rechtschreibung')
+        True
         
         :type: `dict`
         """)
     currencies = property(currencies, doc="""\
         Mapping of currency codes to translated currency names.
         
-        >>> Locale('en').currencies['COP']
-        u'Colombian Peso'
-        >>> Locale('de', 'DE').currencies['COP']
-        u'Kolumbianischer Peso'
-        
+        >>> Locale('en').currencies['COP'] == u('Colombian Peso')
+        True
+        >>> Locale('de', 'DE').currencies['COP'] == u('Kolumbianischer Peso')
+        True
+
         :type: `dict`
         """)
 
     currency_symbols = property(currency_symbols, doc="""\
         Mapping of currency codes to symbols.
         
-        >>> Locale('en', 'US').currency_symbols['USD']
-        u'$'
-        >>> Locale('es', 'CO').currency_symbols['USD']
-        u'US$'
+        >>> Locale('en', 'US').currency_symbols['USD'] == u('$')
+        True
+        >>> Locale('es', 'CO').currency_symbols['USD'] == u('US$')
+        True
         
         :type: `dict`
         """)
     number_symbols = property(number_symbols, doc="""\
         Symbols used in number formatting.
         
-        >>> Locale('fr', 'FR').number_symbols['decimal']
-        u','
+        >>> Locale('fr', 'FR').number_symbols['decimal'] == u(',')
+        True
         
         :type: `dict`
         """)
         Locale patterns for decimal number formatting.
         
         >>> Locale('en', 'US').decimal_formats[None]
-        <NumberPattern u'#,##0.###'>
+        <NumberPattern #,##0.###>
         
         :type: `dict`
         """)
     currency_formats = property(currency_formats, doc=r"""\
         Locale patterns for currency number formatting.
         
-        >>> print Locale('en', 'US').currency_formats[None]
-        <NumberPattern u'\xa4#,##0.00'>
+        >>> print(Locale('en', 'US').currency_formats[None])
+        <NumberPattern \xa4#,##0.00>
         
         :type: `dict`
         """)
         Locale patterns for percent number formatting.
         
         >>> Locale('en', 'US').percent_formats[None]
-        <NumberPattern u'#,##0%'>
+        <NumberPattern #,##0%>
         
         :type: `dict`
         """)
         Locale patterns for scientific number formatting.
         
         >>> Locale('en', 'US').scientific_formats[None]
-        <NumberPattern u'#E0'>
+        <NumberPattern #E0>
         
         :type: `dict`
         """)
     periods = property(periods, doc="""\
         Locale display names for day periods (AM/PM).
         
-        >>> Locale('en', 'US').periods['am']
-        u'AM'
+        >>> Locale('en', 'US').periods['am'] == u('AM')
+        True
         
         :type: `dict`
         """)
     days = property(days, doc="""\
         Locale display names for weekdays.
         
-        >>> Locale('de', 'DE').days['format']['wide'][3]
-        u'Donnerstag'
+        >>> Locale('de', 'DE').days['format']['wide'][3] == u('Donnerstag')
+        True
         
         :type: `dict`
         """)
     months = property(months, doc="""\
         Locale display names for months.
         
-        >>> Locale('de', 'DE').months['format']['wide'][10]
-        u'Oktober'
+        >>> Locale('de', 'DE').months['format']['wide'][10] == ('Oktober')
+        True
         
         :type: `dict`
         """)
     quarters = property(quarters, doc="""\
         Locale display names for quarters.
         
-        >>> Locale('de', 'DE').quarters['format']['wide'][1]
-        u'1. Quartal'
+        >>> Locale('de', 'DE').quarters['format']['wide'][1] == u('1. Quartal')
+        True
         
         :type: `dict`
         """)
     eras = property(eras, doc="""\
         Locale display names for eras.
         
-        >>> Locale('en', 'US').eras['wide'][1]
-        u'Anno Domini'
-        >>> Locale('en', 'US').eras['abbreviated'][0]
-        u'BC'
+        >>> Locale('en', 'US').eras['wide'][1] == u('Anno Domini')
+        True
+        >>> Locale('en', 'US').eras['abbreviated'][0] == u('BC')
+        True
         
         :type: `dict`
         """)
     time_zones = property(time_zones, doc="""\
         Locale display names for time zones.
         
-        >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
-        u'British Summer Time'
-        >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
-        u"St. John's"
+        >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight'] == u('British Summer Time')
+        True
+        >>> Locale('en', 'US').time_zones['America/St_Johns']['city'] == u("St. John's")
+        True
         
         :type: `dict`
         """)
         Meta time zones are basically groups of different Olson time zones that
         have the same GMT offset and daylight savings time.
         
-        >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
-        u'Central European Summer Time'
+        >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight'] == u('Central European Summer Time')
+        True
         
         :type: `dict`
         :since: version 0.9
     zone_formats = property(zone_formats, doc=r"""\
         Patterns related to the formatting of time zones.
         
-        >>> Locale('en', 'US').zone_formats['fallback']
-        u'%(1)s (%(0)s)'
-        >>> Locale('pt', 'BR').zone_formats['region']
-        u'Hor\xe1rio %s'
+        >>> Locale('en', 'US').zone_formats['fallback'] == u('%(1)s (%(0)s)')
+        True
+        >>> Locale('pt', 'BR').zone_formats['region'] == u('Hor\xe1rio %s')
+        True
         
         :type: `dict`
         :since: version 0.9
         Locale patterns for date formatting.
         
         >>> Locale('en', 'US').date_formats['short']
-        <DateTimePattern u'M/d/yy'>
+        <DateTimePattern M/d/yy>
         >>> Locale('fr', 'FR').date_formats['long']
-        <DateTimePattern u'd MMMM y'>
+        <DateTimePattern d MMMM y>
         
         :type: `dict`
         """)
         Locale patterns for time formatting.
         
         >>> Locale('en', 'US').time_formats['short']
-        <DateTimePattern u'h:mm a'>
+        <DateTimePattern h:mm a>
         >>> Locale('fr', 'FR').time_formats['long']
-        <DateTimePattern u'HH:mm:ss z'>
+        <DateTimePattern HH:mm:ss z>
         
         :type: `dict`
         """)
     datetime_formats = property(datetime_formats, doc="""\
         Locale patterns for datetime formatting.
         
-        >>> Locale('en').datetime_formats['full']
-        u'{1} {0}'
-        >>> Locale('th').datetime_formats['medium']
-        u'{1}, {0}'
+        >>> Locale('en').datetime_formats['full'] == u('{1} {0}')
+        True
+        >>> Locale('th').datetime_formats['medium'] == u('{1}, {0}')
+        True
         
         :type: `dict`
         """)

File babel/dates.py

 def get_period_names(locale=LC_TIME):
     """Return the names for day periods (AM/PM) used by the locale.
     
-    >>> get_period_names(locale='en_US')['am']
-    u'AM'
+    >>> get_period_names(locale='en_US')['am'] == u('AM')
+    True
     
     :param locale: the `Locale` object, or a locale string
     :return: the dictionary of period names
 def get_day_names(width='wide', context='format', locale=LC_TIME):
     """Return the day names used by the locale for the specified format.
     
-    >>> get_day_names('wide', locale='en_US')[1]
-    u'Tuesday'
-    >>> get_day_names('abbreviated', locale='es')[1]
-    u'mar'
-    >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
-    u'D'
+    >>> get_day_names('wide', locale='en_US')[1] == u('Tuesday')
+    True
+    >>> get_day_names('abbreviated', locale='es')[1] == u('mar')
+    True
+    >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] == u('D')
+    True
     
     :param width: the width to use, one of "wide", "abbreviated", or "narrow"
     :param context: the context, either "format" or "stand-alone"
 def get_month_names(width='wide', context='format', locale=LC_TIME):
     """Return the month names used by the locale for the specified format.
     
-    >>> get_month_names('wide', locale='en_US')[1]
-    u'January'
-    >>> get_month_names('abbreviated', locale='es')[1]
-    u'ene'
-    >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
-    u'J'
+    >>> get_month_names('wide', locale='en_US')[1] == u('January')
+    True
+    >>> get_month_names('abbreviated', locale='es')[1] == u('ene')
+    True
+    >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] == u('J')
+    True
     
     :param width: the width to use, one of "wide", "abbreviated", or "narrow"
     :param context: the context, either "format" or "stand-alone"
 def get_quarter_names(width='wide', context='format', locale=LC_TIME):
     """Return the quarter names used by the locale for the specified format.
     
-    >>> get_quarter_names('wide', locale='en_US')[1]
-    u'1st quarter'
-    >>> get_quarter_names('abbreviated', locale='de_DE')[1]
-    u'Q1'
+    >>> get_quarter_names('wide', locale='en_US')[1] == u('1st quarter')
+    True
+    >>> get_quarter_names('abbreviated', locale='de_DE')[1] == u('Q1')
+    True
     
     :param width: the width to use, one of "wide", "abbreviated", or "narrow"
     :param context: the context, either "format" or "stand-alone"
 def get_era_names(width='wide', locale=LC_TIME):
     """Return the era names used by the locale for the specified format.
     
-    >>> get_era_names('wide', locale='en_US')[1]
-    u'Anno Domini'
-    >>> get_era_names('abbreviated', locale='de_DE')[1]
-    u'n. Chr.'
+    >>> get_era_names('wide', locale='en_US')[1] == u('Anno Domini')
+    True
+    >>> get_era_names('abbreviated', locale='de_DE')[1] == u('n. Chr.')
+    True
     
     :param width: the width to use, either "wide", "abbreviated", or "narrow"
     :param locale: the `Locale` object, or a locale string
     format.
     
     >>> get_date_format(locale='en_US')
-    <DateTimePattern u'MMM d, y'>
+    <DateTimePattern MMM d, y>
     >>> get_date_format('full', locale='de_DE')
-    <DateTimePattern u'EEEE, d. MMMM y'>
+    <DateTimePattern EEEE, d. MMMM y>
     
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
     """Return the datetime formatting patterns used by the locale for the
     specified format.
     
-    >>> get_datetime_format(locale='en_US')
-    u'{1} {0}'
+    >>> get_datetime_format(locale='en_US') == u('{1} {0}')
+    True
     
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
     format.
     
     >>> get_time_format(locale='en_US')
-    <DateTimePattern u'h:mm:ss a'>
+    <DateTimePattern h:mm:ss a>
     >>> get_time_format('full', locale='de_DE')
-    <DateTimePattern u'HH:mm:ss zzzz'>
+    <DateTimePattern HH:mm:ss zzzz>
     
     :param format: the format to use, one of "full", "long", "medium", or
                    "short"
     as string indicating the offset from GMT.
     
     >>> dt = datetime(2007, 4, 1, 15, 30)
-    >>> get_timezone_gmt(dt, locale='en')
-    u'GMT+00:00'
+    >>> get_timezone_gmt(dt, locale='en') == u('GMT+00:00')
+    True
     
     >>> from pytz import timezone
     >>> tz = timezone('America/Los_Angeles')
     >>> dt = datetime(2007, 4, 1, 15, 30, tzinfo=tz)
-    >>> get_timezone_gmt(dt, locale='en')
-    u'GMT-08:00'
-    >>> get_timezone_gmt(dt, 'short', locale='en')
-    u'-0800'
+    >>> get_timezone_gmt(dt, locale='en') == u('GMT-08:00')
+    True
+    >>> get_timezone_gmt(dt, 'short', locale='en') == u('-0800')
+    True
     
     The long format depends on the locale, for example in France the acronym
     UTC string is used instead of GMT:
     
-    >>> get_timezone_gmt(dt, 'long', locale='fr_FR')
-    u'UTC-08:00'
+    >>> get_timezone_gmt(dt, 'long', locale='fr_FR') == u('UTC-08:00')
+    True
     
     :param datetime: the ``datetime`` object; if `None`, the current date and
                      time in UTC is used
     
     >>> from pytz import timezone
     >>> tz = timezone('America/St_Johns')
-    >>> get_timezone_location(tz, locale='de_DE')
-    u"Kanada (St. John's)"
+    >>> get_timezone_location(tz, locale='de_DE') == u("Kanada (St. John's)")
+    True
     >>> tz = timezone('America/Mexico_City')
-    >>> get_timezone_location(tz, locale='de_DE')
-    u'Mexiko (Mexiko-Stadt)'
+    >>> get_timezone_location(tz, locale='de_DE') == u('Mexiko (Mexiko-Stadt)')
+    True
     
     If the timezone is associated with a country that uses only a single
     timezone, just the localized country name is returned:
     
     >>> tz = timezone('Europe/Berlin')
-    >>> get_timezone_name(tz, locale='de_DE')
-    u'Deutschland'
+    >>> get_timezone_name(tz, locale='de_DE') == u('Deutschland')
+    True
     
     :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
                          the timezone; if `None`, the current date and time in
     
     >>> from pytz import timezone
     >>> dt = time(15, 30, tzinfo=timezone('America/Los_Angeles'))
-    >>> get_timezone_name(dt, locale='en_US')
-    u'Pacific Standard Time'
-    >>> get_timezone_name(dt, width='short', locale='en_US')
-    u'PST'
+    >>> get_timezone_name(dt, locale='en_US') == u('Pacific Standard Time')
+    True
+    >>> get_timezone_name(dt, width='short', locale='en_US') == u('PST')
+    True
     
     If this function gets passed only a `tzinfo` object and no concrete
     `datetime`,  the returned display name is indenpendent of daylight savings
     time of events that recur across DST changes:
     
     >>> tz = timezone('America/Los_Angeles')
-    >>> get_timezone_name(tz, locale='en_US')
-    u'Pacific Time'
-    >>> get_timezone_name(tz, 'short', locale='en_US')
-    u'PT'
+    >>> get_timezone_name(tz, locale='en_US') == u('Pacific Time')
+    True
+    >>> get_timezone_name(tz, 'short', locale='en_US') == u('PT')
+    True
     
     If no localized display name for the timezone is available, and the timezone
     is associated with a country that uses only a single timezone, the name of
     that country is returned, formatted according to the locale:
     
     >>> tz = timezone('Europe/Berlin')
-    >>> get_timezone_name(tz, locale='de_DE')
-    u'Deutschland'
-    >>> get_timezone_name(tz, locale='pt_BR')
-    u'Hor\xe1rio Alemanha'
+    >>> get_timezone_name(tz, locale='de_DE') == u('Deutschland')
+    True
+    >>> get_timezone_name(tz, locale='pt_BR') == u('Hor\xe1rio Alemanha')
+    True
     
     On the other hand, if the country uses multiple timezones, the city is also
     included in the representation:
     
     >>> tz = timezone('America/St_Johns')
-    >>> get_timezone_name(tz, locale='de_DE')
-    u"Kanada (St. John's)"
+    >>> get_timezone_name(tz, locale='de_DE') == u("Kanada (St. John's)")
+    True
     
     The `uncommon` parameter can be set to `True` to enable the use of timezone
     representations that are not commonly used by the requested locale. For
     common use, so a generic name would be chosen by default:
     
     >>> tz = timezone('Europe/Paris')
-    >>> get_timezone_name(tz, 'short', locale='fr_CA')
-    u'France'
-    >>> get_timezone_name(tz, 'short', uncommon=True, locale='fr_CA')
-    u'HEC'
+    >>> get_timezone_name(tz, 'short', locale='fr_CA') == u('France')
+    True
+    >>> get_timezone_name(tz, 'short', uncommon=True, locale='fr_CA') == u('HEC')
+    True
     
     :param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
                          the timezone; if a ``tzinfo`` object is used, the
 def format_date(date=None, format='medium', locale=LC_TIME):
     """Return a date formatted according to the given pattern.
     
-    >>> d = date(2007, 04, 01)
-    >>> format_date(d, locale='en_US')
-    u'Apr 1, 2007'
-    >>> format_date(d, format='full', locale='de_DE')
-    u'Sonntag, 1. April 2007'
+    >>> d = date(2007, 4, 1)
+    >>> format_date(d, locale='en_US') == u('Apr 1, 2007')
+    True
+    >>> format_date(d, format='full', locale='de_DE') == u('Sonntag, 1. April 2007')
+    True
     
     If you don't want to use the locale default formats, you can specify a
     custom date pattern:
     
-    >>> format_date(d, "EEE, MMM d, ''yy", locale='en')
-    u"Sun, Apr 1, '07"
+    >>> format_date(d, "EEE, MMM d, ''yy", locale='en') == u("Sun, Apr 1, '07")
+    True
     
     :param date: the ``date`` or ``datetime`` object; if `None`, the current
                  date is used
                     locale=LC_TIME):
     r"""Return a date formatted according to the given pattern.
     
-    >>> dt = datetime(2007, 04, 01, 15, 30)
-    >>> format_datetime(dt, locale='en_US')
-    u'Apr 1, 2007 3:30:00 PM'
+    >>> dt = datetime(2007, 4, 1, 15, 30)
+    >>> format_datetime(dt, locale='en_US') == u('Apr 1, 2007 3:30:00 PM')
+    True
     
     For any pattern requiring the display of the time-zone, the third-party
     ``pytz`` package is needed to explicitly specify the time-zone:
     
     >>> from pytz import timezone
     >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Paris'),
-    ...                 locale='fr_FR')
-    u'dimanche 1 avril 2007 17:30:00 Heure avanc\xe9e de l\u2019Europe centrale'
+    ...                 locale='fr_FR') == u('dimanche 1 avril 2007 17:30:00 Heure avanc\xe9e de l\u2019Europe centrale')
+    True
     >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
-    ...                 tzinfo=timezone('US/Eastern'), locale='en')
-    u'2007.04.01 AD at 11:30:00 EDT'
+    ...                 tzinfo=timezone('US/Eastern'), locale='en') == u('2007.04.01 AD at 11:30:00 EDT')
+    True
     
     :param datetime: the `datetime` object; if `None`, the current date and
                      time is used
     r"""Return a time formatted according to the given pattern.
     
     >>> t = time(15, 30)
-    >>> format_time(t, locale='en_US')
-    u'3:30:00 PM'
-    >>> format_time(t, format='short', locale='de_DE')
-    u'15:30'
+    >>> format_time(t, locale='en_US') == u('3:30:00 PM')
+    True
+    >>> format_time(t, format='short', locale='de_DE') == u('15:30')
+    True
     
     If you don't want to use the locale default formats, you can specify a
     custom time pattern:
     
-    >>> format_time(t, "hh 'o''clock' a", locale='en')
-    u"03 o'clock PM"
+    >>> format_time(t, "hh 'o''clock' a", locale='en') == u("03 o'clock PM")
+    True
     
     For any pattern requiring the display of the time-zone, the third-party
     ``pytz`` package is needed to explicitly specify the time-zone:
     >>> t = datetime(2007, 4, 1, 15, 30)
     >>> tzinfo = timezone('Europe/Paris')
     >>> t = tzinfo.localize(t)
-    >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
-    u'15:30:00 Heure avanc\xe9e de l\u2019Europe centrale'
+    >>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR') == u('15:30:00 Heure avanc\xe9e de l\u2019Europe centrale')
+    True
     >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'),
-    ...             locale='en')
-    u"09 o'clock AM, Eastern Daylight Time"
+    ...             locale='en') == u("09 o'clock AM, Eastern Daylight Time")
+    True
     
     As that example shows, when this function gets passed a
     ``datetime.datetime`` value, the actual time in the formatted string is
     
     >>> t = time(15, 30)
     >>> format_time(t, format='full', tzinfo=timezone('Europe/Paris'),
-    ...             locale='fr_FR')
-    u'15:30:00 Heure normale de l\u2019Europe centrale'
+    ...             locale='fr_FR') == u('15:30:00 Heure normale de l\u2019Europe centrale')
+    True
     >>> format_time(t, format='full', tzinfo=timezone('US/Eastern'),
-    ...             locale='en_US')
-    u'3:30:00 PM Eastern Standard Time'
+    ...             locale='en_US') == u('3:30:00 PM Eastern Standard Time')
+    True
     
     :param time: the ``time`` or ``datetime`` object; if `None`, the current
                  time in UTC is used
 def format_timedelta(delta, granularity='second', threshold=.85, locale=LC_TIME):
     """Return a time delta according to the rules of the given locale.
 
-    >>> format_timedelta(timedelta(weeks=12), locale='en_US')
-    u'3 mths'
-    >>> format_timedelta(timedelta(seconds=1), locale='es')
-    u'1 s'
+    >>> format_timedelta(timedelta(weeks=12), locale='en_US') == u('3 mths')
+    True
+    >>> format_timedelta(timedelta(seconds=1), locale='es') == u('1 s')
+    True
 
     The granularity parameter can be provided to alter the lowest unit
     presented, which defaults to a second.
     
     >>> format_timedelta(timedelta(hours=3), granularity='day',
-    ...                  locale='en_US')
-    u'1 day'
+    ...                  locale='en_US') == u('1 day')
+    True
 
     The threshold parameter can be used to determine at which value the
     presentation switches to the next higher unit. A higher threshold factor
     means the presentation will switch later. For example:
 
-    >>> format_timedelta(timedelta(hours=23), threshold=0.9, locale='en_US')
-    u'1 day'
-    >>> format_timedelta(timedelta(hours=23), threshold=1.1, locale='en_US')
-    u'23 hrs'
+    >>> format_timedelta(timedelta(hours=23), threshold=0.9, locale='en_US') == u('1 day')
+    True
+    >>> format_timedelta(timedelta(hours=23), threshold=1.1, locale='en_US') == u('23 hrs')
+    True
 
     :param delta: a ``timedelta`` object representing the time difference to
                   format, or the delta in seconds as an `int` value
         month_idx = format.index('l')
     day_idx = format.index('d')
 
-    indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')]
-    indexes.sort()
+    indexes = sorted([(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')])
     indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
 
     # FIXME: this currently only supports numbers, but should also support month
     min_idx = format.index('m')
     sec_idx = format.index('s')
 
-    indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')]
-    indexes.sort()
+    indexes = sorted([(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')])
     indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)])
 
     # FIXME: support 12 hour clock, and 0-based hour specification
         self.format = format
 
     def __repr__(self):
-        return '<%s %r>' % (type(self).__name__, self.pattern)
+        return '<%s %s>' % (type(self).__name__, self.pattern)
 
     def __unicode__(self):
         return self.pattern
 def parse_pattern(pattern):
     """Parse date, time, and datetime format patterns.
     
-    >>> parse_pattern("MMMMd").format
-    u'%(MMMM)s%(d)s'
-    >>> parse_pattern("MMM d, yyyy").format
-    u'%(MMM)s %(d)s, %(yyyy)s'
+    >>> parse_pattern("MMMMd").format == u('%(MMMM)s%(d)s')
+    True
+    >>> parse_pattern("MMM d, yyyy").format == u('%(MMM)s %(d)s, %(yyyy)s')
+    True
     
     Pattern can contain literal strings in single quotes:
     
-    >>> parse_pattern("H:mm' Uhr 'z").format
-    u'%(H)s:%(mm)s Uhr %(z)s'
+    >>> parse_pattern("H:mm' Uhr 'z").format == u('%(H)s:%(mm)s Uhr %(z)s')
+    True
     
     An actual single quote can be used by using two adjacent single quote
     characters:
     
-    >>> parse_pattern("hh' o''clock'").format
-    u"%(hh)s o'clock"
+    >>> parse_pattern("hh' o''clock'").format == u("%(hh)s o'clock")
+    True
     
     :param pattern: the formatting pattern to parse
     """

File babel/localedata.py

 """
 
 import os
-from babel.compat import pickle, DictMixin
+from babel.compat import pickle, DictMixin, PY3, u
 try:
     import threading
 except ImportError:
     collection of pickle files inside the ``babel`` package.
     
     >>> d = load('en_US')
-    >>> d['languages']['sv']
-    u'Swedish'
+    >>> d['languages']['sv'] == u('Swedish')
+    True
     
     Note that the results are cached, and subsequent requests for the same
     locale return the same dictionary:
     
     >>> d = {1: 'foo', 3: 'baz'}
     >>> merge(d, {1: 'Foo', 2: 'Bar'})
-    >>> items = d.items(); items.sort(); items
+    >>> items = sorted(d.items()); items
     [(1, 'Foo'), (2, 'Bar'), (3, 'baz')]
     
     :param dict1: the dictionary to merge into
 
     def __init__(self, data, base=None):
         dict.__init__(self, data)
+        if PY3:
+          DictMixin.__init__(self, data)
         if base is None:
             base = data
         self.base = base

File babel/messages/catalog.py

 import time
 
 from babel import __version__ as VERSION
-from babel.compat import u, string_types
+from babel.compat import u, string_types, PY3
 from babel.core import Locale
 from babel.dates import format_datetime
 from babel.messages.plurals import get_plural
         self.context = context
 
     def __repr__(self):
-        return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
+        return '<%s %s (flags: %r)>' % (type(self).__name__, self.id,
                                         list(self.flags))
 
     def __cmp__(self, obj):
         """Compare Messages, taking into account plural ids"""
+        
+        def cmp(a, b):
+            return ((a > b) - (a < b))
+
         if isinstance(obj, Message):
             plural = self.pluralizable
             obj_plural = obj.pluralizable
                 return cmp(self.id, obj.id[0])
         return cmp(self.id, obj.id)
 
+    def __gt__(self, other):
+        return self.__cmp__(other) > 0
+
+    def __lt__(self, other):
+        return self.__cmp__(other) < 0
+
+    def __ge__(self, other):
+        return self.__cmp__(other) >= 0
+
+    def __le__(self, other):
+        return self.__cmp__(other) <= 0
+
+    def __eq__(self, other):
+        return self.__cmp__(other) == 0
+
+    def __ne__(self, other):
+        return self.__cmp__(other) != 0
+
     def clone(self):
-        return Message(*tuple(map(copy, (self.id, self.string, self.locations,
+        return Message(*map(copy, (self.id, self.string, self.locations,
                                    self.flags, self.auto_comments,
                                    self.user_comments, self.previous_id,
-                                   self.lineno, self.context))))
+                                   self.lineno, self.context)))
 
     def check(self, catalog=None):
         """Run various validation checks on the message.  Some validations
         >>> msg.fuzzy
         True
         >>> msg
-        <Message 'foo' (flags: ['fuzzy'])>
+        <Message foo (flags: ['fuzzy'])>
 
         :type:  `bool`
         """)
 
     >>> catalog = Catalog(project='Foobar', version='1.0',
     ...                   copyright_holder='Foo Company')
-    >>> print catalog.header_comment #doctest: +ELLIPSIS
+    >>> print(catalog.header_comment) #doctest: +ELLIPSIS
     # Translations template for Foobar.
     # Copyright (C) ... Foo Company
     # This file is distributed under the same license as the Foobar project.
     ... # This file is distributed under the same license as the PROJECT
     ... # project.
     ... #'''
-    >>> print catalog.header_comment
+    >>> print(catalog.header_comment)
     # The POT for my really cool Foobar project.
     # Copyright (C) 1990-2003 Foo Company
     # This file is distributed under the same license as the Foobar
     >>> catalog = Catalog(project='Foobar', version='1.0',
     ...                   creation_date=created)
     >>> for name, value in catalog.mime_headers:
-    ...     print '%s: %s' % (name, value)
+    ...     print('%s: %s' % (name, value))
     Project-Id-Version: Foobar 1.0
     Report-Msgid-Bugs-To: EMAIL@ADDRESS
     POT-Creation-Date: 1990-04-01 15:30+0000
     ...                   last_translator='John Doe <jd@example.com>',
     ...                   language_team='de_DE <de@example.com>')
     >>> for name, value in catalog.mime_headers:
-    ...     print '%s: %s' % (name, value)
+    ...     print('%s: %s' % (name, value))
     Project-Id-Version: Foobar 1.0
     Report-Msgid-Bugs-To: EMAIL@ADDRESS
     POT-Creation-Date: 1990-04-01 15:30+0000
         """Add or update the message with the specified ID.
 
         >>> catalog = Catalog()
-        >>> catalog[u'foo'] = Message(u'foo')
-        >>> catalog[u'foo']
-        <Message u'foo' (flags: [])>
+        >>> catalog[u('foo')] = Message(u('foo'))
+        >>> catalog[u('foo')]
+        <Message foo (flags: [])>
 
         If a message with that ID is already in the catalog, it is updated
         to include the locations and flags of the new message.
 
         >>> catalog = Catalog()
-        >>> catalog[u'foo'] = Message(u'foo', locations=[('main.py', 1)])
-        >>> catalog[u'foo'].locations
+        >>> catalog[u('foo')] = Message(u('foo'), locations=[('main.py', 1)])
+        >>> catalog[u('foo')].locations
         [('main.py', 1)]
-        >>> catalog[u'foo'] = Message(u'foo', locations=[('utils.py', 5)])
-        >>> catalog[u'foo'].locations
+        >>> catalog[u('foo')] = Message(u('foo'), locations=[('utils.py', 5)])
+        >>> catalog[u('foo')].locations
         [('main.py', 1), ('utils.py', 5)]
 
         :param id: the message ID
             # special treatment for the header message
             def _parse_header(header_string):
                 # message_from_string only works for str, not for unicode
-                headers = message_from_string(header_string.encode('utf8'))
+                if not PY3:
+                    header_string = header_string.encode('utf8')
+                headers = message_from_string(header_string)
                 decoded_headers = {}
                 for name, value in headers.items():
-                    name = name.decode('utf8')
-                    value = value.decode('utf8')
+                    if not PY3:
+                        name, value = name.decode('utf8'), value.decode('utf8')
                     decoded_headers[name] = value
                 return decoded_headers
             self.mime_headers = list(_parse_header(message.string).items())
         """Add or update the message with the specified ID.
 
         >>> catalog = Catalog()
-        >>> catalog.add(u'foo')
+        >>> catalog.add(u('foo'))
         <Message ...>
-        >>> catalog[u'foo']
-        <Message u'foo' (flags: [])>
+        >>> catalog[u('foo')]
+        <Message foo (flags: [])>
 
         This method simply constructs a `Message` object with the given
         arguments and invokes `__setitem__` with that object.
         >>> template.add(('salad', 'salads'), locations=[('util.py', 42)])
         <Message ...>
         >>> catalog = Catalog(locale='de_DE')
-        >>> catalog.add('blue', u'blau', locations=[('main.py', 98)])
+        >>> catalog.add('blue', u('blau'), locations=[('main.py', 98)])
         <Message ...>
-        >>> catalog.add('head', u'Kopf', locations=[('util.py', 33)])
+        >>> catalog.add('head', u('Kopf'), locations=[('util.py', 33)])
         <Message ...>
-        >>> catalog.add(('salad', 'salads'), (u'Salat', u'Salate'),
+        >>> catalog.add(('salad', 'salads'), (u('Salat'), u('Salate')),
         ...             locations=[('util.py', 38)])
         <Message ...>
 
         [('main.py', 99)]
 
         >>> msg2 = catalog['blue']
-        >>> msg2.string
-        u'blau'
+        >>> print(msg2.string)
+        blau
         >>> msg2.locations
         [('main.py', 100)]
 
         >>> msg3 = catalog['salad']
-        >>> msg3.string
-        (u'Salat', u'Salate')
+        >>> print(msg3.string[0])
+        Salat
+        >>> print(msg3.string[1])
+        Salate
         >>> msg3.locations
         [('util.py', 42)]
 
 
         >>> 'head' in catalog
         False
-        >>> catalog.obsolete.values()
-        [<Message 'head' (flags: [])>]
+        >>> for v in catalog.obsolete.values():
+        ...     print(v)
+        <Message head (flags: [])>
 
         :param template: the reference catalog, usually read from a POT file
         :param no_fuzzy_matching: whether to use fuzzy matching of message IDs
                         else:
                             matchkey = key
                         matches = get_close_matches(matchkey.lower().strip(),
-                                                    list(fuzzy_candidates.keys()), 1)
+                                                    fuzzy_candidates.keys(), 1)
                         if matches:
                             newkey = matches[0]
                             newctxt = fuzzy_candidates[newkey]

File babel/messages/extract.py

 import sys
 from tokenize import generate_tokens, COMMENT, NAME, OP, STRING
 
+from babel.compat import PY3, binary_type
 from babel.util import parse_encoding, pathmatch, relpath
 from textwrap import dedent
 
     ...    print _('Hello, world!')
     ... '''
 
-    >>> from StringIO import StringIO
+    >>> from babel.compat import StringIO
     >>> for message in extract('python', StringIO(source)):
-    ...     print message
-    (3, u'Hello, world!', [])
+    ...     print(message[0])
+    ...     print(message[1])
+    ...     print(message[2])
+    3
+    Hello, world!
+    []
 
     :param method: a string specifying the extraction method (.e.g. "python");
                    if this is a simple name, the extraction function will be
 
     encoding = parse_encoding(fileobj) or options.get('encoding', 'iso-8859-1')
 
-    tokens = generate_tokens(fileobj.readline)
+    def readline():
+        line = fileobj.readline()
+        if PY3 and isinstance(line, binary_type):
+            try:
+                line = line.decode(encoding)
+            except UnicodeDecodeError:
+                import pdb; pdb.set_trace()
+        return line
+
+    tokens = generate_tokens(readline)
     for tok, value, (lineno, _), _, _ in tokens:
         if call_stack == -1 and tok == NAME and value in ('def', 'class'):
             in_def = True
             continue
         elif call_stack == -1 and tok == COMMENT:
             # Strip the comment token from the line
-            value = value.decode(encoding)[1:].strip()
+            if not PY3:
+                value = value.decode(encoding)
+            value = value[1:].strip()
             if in_translator_comments and \
                     translator_comments[-1][0] == lineno - 1:
                 # We're already inside a translator comment, continue appending
                 # aid=617979&group_id=5470
                 value = eval('# coding=%s\n%s' % (encoding, value),
                              {'__builtins__':{}}, {})
-                if isinstance(value, str):
+                if isinstance(value, binary_type):
                     value = value.decode(encoding)
                 buf.append(value)
             elif tok == OP and value == ',':
     last_token = None
     call_stack = -1
 
-    for token in tokenize(fileobj.read().decode(encoding)):
+    data = fileobj.read()
+    if not PY3:
+        data = data.decode(encoding)
+    for token in tokenize(data):
         if token.type == 'operator' and token.value == '(':
             if funcname:
                 message_lineno = token.lineno

File babel/messages/frontend.py

 
     def run(self):
         mappings = self._get_mappings()
-        outfile = open(self.output_file, 'w')
+        outfile = open(self.output_file, 'wb')
         try:
             catalog = Catalog(project=self.distribution.get_name(),
                               version=self.distribution.get_version(),
         catalog.locale = self._locale
         catalog.fuzzy = False
 
-        outfile = open(self.output_file, 'w')
+        outfile = open(self.output_file, 'wb')
         try:
             write_po(outfile, catalog)
         finally:
 
         self._configure_logging(options.loglevel)
         if options.list_locales:
-            identifiers = localedata.locale_identifiers()
+            identifiers = sorted(localedata.locale_identifiers())
             longest = max([len(identifier) for identifier in identifiers])
-            identifiers.sort()
             format = u('%%-%ds %%s') % (longest + 1)
             for identifier in identifiers:
                 locale = Locale.parse(identifier)
         print("commands:")
         longest = max([len(command) for command in self.commands])
         format = "  %%-%ds %%s" % max(8, longest + 1)
-        commands = self.commands.items()
-        commands.sort()
+        commands = sorted(self.commands.items())
         for name, description in commands:
             print(format % (name, description))
 
             parser.error('incorrect number of arguments')
 
         if options.output not in (None, '-'):
-            outfile = open(options.output, 'w')
+            outfile = open(options.output, 'wb')
         else:
             outfile = sys.stdout
 
         self.log.info('creating catalog %r based on %r', options.output_file,
                       options.input_file)
 
-        outfile = open(options.output_file, 'w')
+        outfile = open(options.output_file, 'wb')
         try:
             write_po(outfile, catalog)
         finally:
 def parse_keywords(strings=[]):
     """Parse keywords specifications from the given list of strings.
 
-    >>> kw = parse_keywords(['_', 'dgettext:2', 'dngettext:2,3']).items()
-    >>> kw.sort()
-    >>> for keyword, indices in kw:
-    ...     print (keyword, indices)
+    >>> kw = sorted(parse_keywords(['_', 'dgettext:2', 'dngettext:2,3']).items())
+    >>> for keyword, indices in sorted(kw):
+    ...     print((keyword, indices))
     ('_', None)
     ('dgettext', (2,))
     ('dngettext', (2, 3))

File babel/messages/jslexer.py

     '>>>=', '&', '&=', '|', '|=', '&&', '||', '^', '^=', '(', ')',
     '[', ']', '{', '}', '!', '--', '++', '~', ',', ';', '.', ':'
 ]
-operators.sort(lambda a, b: cmp(-len(a), -len(b)))
+operators.sort(key=lambda a: -len(a))
 
 escapes = {'b': '\b', 'f': '\f', 'n': '\n', 'r': '\r', 't': '\t'}
 

File babel/messages/mofile.py

 import array
 import struct
 
-from babel.compat import long_type, xrange
+from babel.compat import long_type, xrange, u, b, PY3
 from babel.messages.catalog import Catalog, Message
 
 __all__ = ['read_mo', 'write_mo']
                 item = item.strip()
                 if not item:
                     continue
-                if ':' in item:
-                    key, value = item.split(':', 1)
+                if b(':') in item:
+                    key, value = item.split(b(':'), 1)
                     lastkey = key = key.strip().lower()
                     headers[key] = value.strip()
                 elif lastkey:
                     headers[lastkey] += '\n' + item
 
-        if '\x04' in msg: # context
-            ctxt, msg = msg.split('\x04')
+        if b('\x04') in msg: # context
+            ctxt, msg = msg.split(b('\x04'))
         else:
             ctxt = None
 
-        if '\x00' in msg: # plural forms
-            msg = msg.split('\x00')
-            tmsg = tmsg.split('\x00')
+        if b('\x00') in msg: # plural forms
+            msg = msg.split(b('\x00'))
+            tmsg = tmsg.split(b('\x00'))
             if catalog.charset:
                 msg = [x.decode(catalog.charset) for x in msg]
                 tmsg = [x.decode(catalog.charset) for x in tmsg]
     format.
     
     >>> from babel.messages import Catalog
-    >>> from gettext import GNUTranslations
-    >>> from StringIO import StringIO
+    >>> from babel.compat import BytesIO, GNUTranslations
     
     >>> catalog = Catalog(locale='en_US')
     >>> catalog.add('foo', 'Voh')
     <Message ...>
-    >>> catalog.add((u'bar', u'baz'), (u'Bahr', u'Batz'))
+    >>> catalog.add((u('bar'), u('baz')), (u('Bahr'), u('Batz')))
     <Message ...>
     >>> catalog.add('fuz', 'Futz', flags=['fuzzy'])
     <Message ...>
     <Message ...>
     >>> catalog.add(('Fuzz', 'Fuzzes'), ('', ''))
     <Message ...>
-    >>> buf = StringIO()
-    
+    >>> buf = BytesIO()
     >>> write_mo(buf, catalog)
-    >>> buf.seek(0)
+    >>> _ = buf.seek(0)
     >>> translations = GNUTranslations(fp=buf)
-    >>> translations.ugettext('foo')
-    u'Voh'
-    >>> translations.ungettext('bar', 'baz', 1)
-    u'Bahr'
-    >>> translations.ungettext('bar', 'baz', 2)
-    u'Batz'
-    >>> translations.ugettext('fuz')
-    u'fuz'
-    >>> translations.ugettext('Fizz')
-    u'Fizz'
-    >>> translations.ugettext('Fuzz')
-    u'Fuzz'
-    >>> translations.ugettext('Fuzzes')
-    u'Fuzzes'
+    >>> translations.ugettext('foo') == u('Voh')
+    True
+    >>> translations.ungettext('bar', 'baz', 1) == u('Bahr')
+    True
+    >>> translations.ungettext('bar', 'baz', 2) == u('Batz')
+    True
+    >>> translations.ugettext('fuz') == u('fuz')
+    True
+    >>> translations.ugettext('Fizz') == u('Fizz')
+    True
+    >>> translations.ugettext('Fuzz') == u('Fuzz')
+    True
+    >>> translations.ugettext('Fuzzes') == u('Fuzzes')
+    True
     
     :param fileobj: the file-like object to write to
     :param catalog: the `Catalog` instance
         messages[1:] = [m for m in messages[1:] if not m.fuzzy]
     messages.sort()
 
-    ids = strs = ''
+    ids = strs = b('')
     offsets = []
 
     for message in messages:
         # For each string, we need size and file offset.  Each string is NUL
         # terminated; the NUL does not count into the size.
         if message.pluralizable:
-            msgid = '\x00'.join([
+            msgid = b('\x00').join([
                 msgid.encode(catalog.charset) for msgid in message.id
             ])
             msgstrs = []
                     msgstrs.append(message.id[min(int(idx), 1)])
                 else:
                     msgstrs.append(string)
-            msgstr = '\x00'.join([
+            msgstr = b('\x00').join([
                 msgstr.encode(catalog.charset) for msgstr in msgstrs
             ])
         else:
             else:
                 msgstr = message.string.encode(catalog.charset)
         if message.context:
-            msgid = '\x04'.join([message.context.encode(catalog.charset),
+            msgid = b('\x04').join([message.context.encode(catalog.charset),
                                  msgid])
         offsets.append((len(ids), len(msgid), len(strs), len(msgstr)))
-        ids += msgid + '\x00'
-        strs += msgstr + '\x00'
+        ids += msgid + b('\x00')
+        strs += msgstr + b('\x00')
 
     # The header is 7 32-bit unsigned integers.  We don't use hash tables, so
     # the keys start right after the index tables.
         7 * 4,                      # start of key index
         7 * 4 + len(messages) * 8,  # start of value index
         0, 0                        # size and offset of hash table
-    ) + array.array("i", offsets).tostring() + ids + strs)
+    ))
+    if PY3:
+        fileobj.write(array.array("i", offsets).tobytes())
+    else:
+        fileobj.write(array.array("i", offsets).tostring())
+    fileobj.write(ids + strs)

File babel/messages/pofile.py

 def unescape(string):
     r"""Reverse `escape` the given string.
 
-    >>> print unescape('"Say:\\n  \\"hello, world!\\"\\n"')
+    >>> print(unescape('"Say:\\n  \\"hello, world!\\"\\n"'))
     Say:
       "hello, world!"
     <BLANKLINE>
 def denormalize(string):
     r"""Reverse the normalization done by the `normalize` function.
 
-    >>> print denormalize(r'''""
+    >>> print(denormalize(r'''""
     ... "Say:\n"
-    ... "  \"hello, world!\"\n"''')
+    ... "  \"hello, world!\"\n"'''))
     Say:
       "hello, world!"
     <BLANKLINE>
 
-    >>> print denormalize(r'''""
+    >>> print(denormalize(r'''""
     ... "Say:\n"
     ... "  \"Lorem ipsum dolor sit "
     ... "amet, consectetur adipisicing"
-    ... " elit, \"\n"''')
+    ... " elit, \"\n"'''))
     Say:
       "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
     <BLANKLINE>
     """Read messages from a ``gettext`` PO (portable object) file from the given
     file-like object and return a `Catalog`.
 
-    >>> from StringIO import StringIO
+    >>> from babel.compat import StringIO
     >>> buf = StringIO('''
     ... #: main.py:1
     ... #, fuzzy, python-format
     ... msgstr[1] ""
     ... ''')
     >>> catalog = read_po(buf)
-    >>> catalog.revision_date = datetime(2007, 04, 01)
+    >>> catalog.revision_date = datetime(2007, 4, 1)
 
     >>> for message in catalog:
     ...     if message.id:
-    ...         print (message.id, message.string)
-    ...         print ' ', (message.locations, message.flags)
-    ...         print ' ', (message.user_comments, message.auto_comments)
-    (u'foo %(name)s', '')
-      ([(u'main.py', 1)], set([u'fuzzy', u'python-format']))
-      ([], [])
-    ((u'bar', u'baz'), ('', ''))
-      ([(u'main.py', 3)], set([]))
-      ([u'A user comment'], [u'An auto comment'])
+    ...         print('id(s): %s' % (isinstance(message.id, tuple) and u(',').join(message.id) or message.id))
+    ...         print('strings(s): %s' % (isinstance(message.string, tuple) and u(',').join(message.string) or message.string))
+    ...         for loc in message.locations:
+    ...             print('file: %s line: %d' % loc)
+    ...         print('flags: %s' % ' '.join(sorted(message.flags)))
+    ...         print('user comments: %s' % ','.join(message.user_comments))
+    ...         print('auto comments: %s' % ','.join(message.auto_comments))
+    id(s): foo %(name)s
+    strings(s): 
+    file: main.py line: 1
+    flags: fuzzy python-format
+    user comments: 
+    auto comments: 
+    id(s): bar,baz
+    strings(s): ,
+    file: main.py line: 3
+    flags: 
+    user comments: A user comment
+    auto comments: An auto comment
 
     :param fileobj: the file-like object to read the PO file from
     :param locale: the locale identifier or `Locale` object, or `None`
 def normalize(string, prefix='', width=76):
     r"""Convert a string into a format that is appropriate for .po files.
 
-    >>> print normalize('''Say:
+    >>> print(normalize('''Say:
     ...   "hello, world!"
-    ... ''', width=None)
+    ... ''', width=None))
     ""
     "Say:\n"
     "  \"hello, world!\"\n"
 
-    >>> print normalize('''Say:
+    >>> print(normalize('''Say:
     ...   "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
-    ... ''', width=32)
+    ... ''', width=32))
     ""
     "Say:\n"
     "  \"Lorem ipsum dolor sit "
     message catalog to the provided file-like object.
 
     >>> catalog = Catalog()
-    >>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
+    >>> catalog.add(u('foo %(name)s'), locations=[('main.py', 1)],
     ...             flags=('fuzzy',))
     <Message...>
-    >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
+    >>> catalog.add((u('bar'), u('baz')), locations=[('main.py', 3)])
     <Message...>
-    >>> from StringIO import StringIO
-    >>> buf = StringIO()
+    >>> from babel.compat import BytesIO
+    >>> buf = BytesIO()
     >>> write_po(buf, catalog, omit_header=True)
-    >>> print buf.getvalue()
+    >>> print(buf.getvalue().decode('latin-1'))
     #: main.py:1
     #, fuzzy, python-format
     msgid "foo %(name)s"

File babel/messages/tests/checkers.py

 import unittest
 
 from babel import __version__ as VERSION
-from babel.compat import StringIO
+from babel.compat import StringIO, BytesIO, u
 from babel.core import Locale, UnknownLocaleError
 from babel.dates import format_datetime
 from babel.messages import checkers
             except UnknownLocaleError:
                 # Just an alias? Not what we're testing here, let's continue
                 continue
-            po_file = (ur"""\
+            po_file = (u(r"""\
 # %(english_name)s translations for TestProject.
 # Copyright (C) 2007 FooBar, Inc.
 # This file is distributed under the same license as the TestProject
 msgid_plural "foobars"
 msgstr[0] ""
 
-""" % dict(locale       = _locale,
+""") % dict(locale       = _locale,
            english_name = locale.english_name,
            version      = VERSION,
            year         = time.strftime('%Y'),
 
             # This test will fail for revisions <= 406 because so far
             # catalog.num_plurals was neglected
-            catalog = read_po(StringIO(po_file), _locale)
+            catalog = read_po(BytesIO(po_file), _locale)
             message = catalog['foobar']
             checkers.num_plurals(catalog, message)
 
             except UnknownLocaleError:
                 # Just an alias? Not what we're testing here, let's continue
                 continue
-            po_file = (ur"""\
+            po_file = (u(r"""\
 # %(english_name)s translations for TestProject.
 # Copyright (C) 2007 FooBar, Inc.
 # This file is distributed under the same license as the TestProject
 msgstr[1] ""
 msgstr[2] ""
 
-""" % dict(locale       = _locale,
+""") % dict(locale       = _locale,
            english_name = locale.english_name,
            version      = VERSION,
            year         = time.strftime('%Y'),
 
             # This test will fail for revisions <= 406 because so far
             # catalog.num_plurals was neglected
-            catalog = read_po(StringIO(po_file), _locale)
+            catalog = read_po(BytesIO(po_file), _locale)
             message = catalog['foobar']
             checkers.num_plurals(catalog, message)
 

File babel/messages/tests/extract.py

 import sys
 import unittest
 
-from babel.compat import StringIO, u
+from babel.compat import StringIO, BytesIO, u, b
 from babel.messages import extract
 
 
 msg10 = dngettext(getDomain(), 'Page', 'Pages', 3)
 """)
         messages = list(extract.extract_python(buf,
-                                               list(extract.DEFAULT_KEYWORDS.keys()),
+                                               extract.DEFAULT_KEYWORDS.keys(),
                                                [], {}))
         self.assertEqual([
                 (1, '_', None, []),
         self.assertEqual([u('NOTE: hello')], messages[0][3])
 
     def test_utf8_message_with_utf8_bom(self):
-        buf = StringIO(codecs.BOM_UTF8 + """
+        buf = BytesIO(codecs.BOM_UTF8 + u("""
 # NOTE: hello
-msg = _('Bonjour à tous')
-""")
+msg = _('Bonjour \xe0 tous')
+""").encode('utf-8'))
         messages = list(extract.extract_python(buf, ('_',), ['NOTE:'], {}))
         self.assertEqual(u('Bonjour \xe0 tous'), messages[0][2])
         self.assertEqual([u('NOTE: hello')], messages[0][3])
 
     def test_utf8_raw_strings_match_unicode_strings(self):
-        buf = StringIO(codecs.BOM_UTF8 + """
-msg = _('Bonjour à tous')
-msgu = _(u'Bonjour à tous')
-""")
+        buf = BytesIO(codecs.BOM_UTF8 + u("""
+msg = _('Bonjour \xe0 tous')
+msgu = _(u'Bonjour \xe0 tous')
+""").encode('utf-8'))
         messages = list(extract.extract_python(buf, ('_',), ['NOTE:'], {}))
         self.assertEqual(u('Bonjour \xe0 tous'), messages[0][2])
         self.assertEqual(messages[0][2], messages[1][2])

File babel/messages/tests/mofile.py

 import os
 import unittest
 
-from babel.compat import StringIO, u
+from babel.compat import BytesIO, u, text_type
 from babel.messages import mofile, Catalog
 
 
 
     def test_basics(self):
         mo_file = open(os.path.join(self.datadir, 'project', 'i18n', 'de',
-                                    'LC_MESSAGES', 'messages.mo'))
+                                    'LC_MESSAGES', 'messages.mo'), 'rb')
         try:
             catalog = mofile.read_mo(mo_file)
             self.assertEqual(2, len(catalog))
         catalog.add((u('There is'), u('There are')), (u('Es gibt'), u('Es gibt')))
         catalog.add(u('Fizz'), '')
         catalog.add(('Fuzz', 'Fuzzes'), ('', ''))
-        buf = StringIO()
+        buf = BytesIO()
         mofile.write_mo(buf, catalog)
         buf.seek(0)
         translations = gettext.GNUTranslations(fp=buf)
         self.assertEqual(u('Voh'), translations.ugettext('foo'))
-        assert isinstance(translations.ugettext('foo'), unicode)
+        assert isinstance(translations.ugettext('foo'), text_type)
         self.assertEqual(u('Es gibt'), translations.ungettext('There is', 'There are', 1))
-        assert isinstance(translations.ungettext('There is', 'There are', 1), unicode)
+        assert isinstance(translations.ungettext('There is', 'There are', 1), text_type)
         self.assertEqual(u('Fizz'), translations.ugettext('Fizz'))
-        assert isinstance(translations.ugettext('Fizz'), unicode)
+        assert isinstance(translations.ugettext('Fizz'), text_type)
         self.assertEqual(u('Fuzz'), translations.ugettext('Fuzz'))
-        assert isinstance(translations.ugettext('Fuzz'), unicode)
+        assert isinstance(translations.ugettext('Fuzz'), text_type)
         self.assertEqual(u('Fuzzes'), translations.ugettext('Fuzzes'))
-        assert isinstance(translations.ugettext('Fuzzes'), unicode)
+        assert isinstance(translations.ugettext('Fuzzes'), text_type)
 
     def test_more_plural_forms(self):
         catalog2 = Catalog(locale='ru_RU')
         catalog2.add(('Fuzz', 'Fuzzes'), ('', '', ''))
-        buf = StringIO()
+        buf = BytesIO()
         mofile.write_mo(buf, catalog2)
 
 

File babel/messages/tests/pofile.py

 import doctest
 import unittest
 
-from babel.compat import StringIO, u
+from babel.compat import StringIO, BytesIO, u, b
 from babel.messages.catalog import Catalog, Message
 from babel.messages import pofile
 from babel.util import FixedOffsetTimezone
         self.assertEqual('mydomain', catalog.domain)
 
     def test_applies_specified_encoding_during_read(self):
-        buf = StringIO(u('''
+        buf = BytesIO(u('''
 msgid ""
 msgstr ""
 "Project-Id-Version:  3.15\\n"
         self.assertEqual('Menu', message.context)
 
         # And verify it pass through write_po
-        out_buf = StringIO()
+        out_buf = BytesIO()
         pofile.write_po(out_buf, catalog, omit_header=True)
-        assert out_buf.getvalue().strip() == buf.getvalue().strip(), \
+        assert out_buf.getvalue().strip() == b(buf.getvalue().strip()), \
                                                             out_buf.getvalue()
 
     def test_with_context_two(self):
         self.assertEqual('Mannu', message.context)
         
         # And verify it pass through write_po
-        out_buf = StringIO()
+        out_buf = BytesIO()
         pofile.write_po(out_buf, catalog, omit_header=True)
-        assert out_buf.getvalue().strip() == buf.getvalue().strip(), out_buf.getvalue()
+        assert out_buf.getvalue().strip() == b(buf.getvalue().strip()), out_buf.getvalue()
 
     def test_single_plural_form(self):
         buf = StringIO(r'''msgid "foo"
         catalog = Catalog()
         catalog.add(u('foo'), locations=[('main.py', 1)])
         catalog.add(u('foo'), locations=[('utils.py', 3)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual('''#: main.py:1 utils.py:3
+        self.assertEqual(b('''#: main.py:1 utils.py:3
 msgid "foo"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
 
     def test_write_po_file_with_specified_charset(self):
         catalog = Catalog(charset='iso-8859-1')
         catalog.add('foo', u('\xe4\xf6\xfc'), locations=[('main.py', 1)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=False)
         po_file = buf.getvalue().strip()
-        assert r'"Content-Type: text/plain; charset=iso-8859-1\n"' in po_file
+        assert b(r'"Content-Type: text/plain; charset=iso-8859-1\n"') in po_file
         assert u('msgstr "\xe4\xf6\xfc"').encode('iso-8859-1') in po_file
 
     def test_duplicate_comments(self):
         catalog = Catalog()
         catalog.add(u('foo'), auto_comments=['A comment'])
         catalog.add(u('foo'), auto_comments=['A comment'])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual('''#. A comment
+        self.assertEqual(b('''#. A comment
 msgid "foo"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
 
     def test_wrap_long_lines(self):
         text = """Here's some text where       
 """
         catalog = Catalog()
         catalog.add(text, locations=[('main.py', 1)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, no_location=True, omit_header=True,
                          width=42)
-        self.assertEqual(r'''msgid ""
+        self.assertEqual(b(r'''msgid ""
 "Here's some text where       \n"
 "white space and line breaks matter, and"
 " should\n"
 "\n"
 "not be removed\n"
 "\n"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
 
     def test_wrap_long_lines_with_long_word(self):
         text = """Here's some text that
 """
         catalog = Catalog()
         catalog.add(text, locations=[('main.py', 1)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, no_location=True, omit_header=True,
                          width=32)
-        self.assertEqual(r'''msgid ""
+        self.assertEqual(b(r'''msgid ""
 "Here's some text that\n"
 "includesareallylongwordthatmightbutshouldnt"
 " throw us into an infinite "
 "loop\n"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
         
     def test_wrap_long_lines_in_header(self):
         """
         """
         catalog = Catalog(project='AReallyReallyLongNameForAProject',
                           revision_date=datetime(2007, 4, 1))
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog)
-        self.assertEqual('''\
+        self.assertEqual(b('''\
 # Translations template for AReallyReallyLongNameForAProject.
 # Copyright (C) 2007 ORGANIZATION
 # This file is distributed under the same license as the
 # AReallyReallyLongNameForAProject project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2007.
 #
-#, fuzzy''', '\n'.join(buf.getvalue().splitlines()[:7]))
+#, fuzzy'''), b('\n').join(buf.getvalue().splitlines()[:7]))
 
     def test_wrap_locations_with_hyphens(self):
         catalog = Catalog()
         catalog.add(u('foo'), locations=[
             ('doupy/templates/job-offers/helpers.html', 22)
         ])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual('''#: doupy/templates/base/navmenu.inc.html.py:60
+        self.assertEqual(b('''#: doupy/templates/base/navmenu.inc.html.py:60
 #: doupy/templates/job-offers/helpers.html:22
 msgid "foo"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
         
     def test_no_wrap_and_width_behaviour_on_comments(self):
         catalog = Catalog()
         catalog.add("Pretty dam long message id, which must really be big "
                     "to test this wrap behaviour, if not it won't work.",
                     locations=[("fake.py", n) for n in range(1, 30)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, width=None, omit_header=True)
-        self.assertEqual("""\
+        self.assertEqual(b("""\
 #: fake.py:1 fake.py:2 fake.py:3 fake.py:4 fake.py:5 fake.py:6 fake.py:7
 #: fake.py:8 fake.py:9 fake.py:10 fake.py:11 fake.py:12 fake.py:13 fake.py:14
 #: fake.py:15 fake.py:16 fake.py:17 fake.py:18 fake.py:19 fake.py:20 fake.py:21
 msgid "pretty dam long message id, which must really be big to test this wrap behaviour, if not it won't work."
 msgstr ""
 
-""", buf.getvalue().lower())
-        buf = StringIO()
+"""), buf.getvalue().lower())
+        buf = BytesIO()
         pofile.write_po(buf, catalog, width=100, omit_header=True)
-        self.assertEqual("""\
+        self.assertEqual(b("""\
 #: fake.py:1 fake.py:2 fake.py:3 fake.py:4 fake.py:5 fake.py:6 fake.py:7 fake.py:8 fake.py:9 fake.py:10
 #: fake.py:11 fake.py:12 fake.py:13 fake.py:14 fake.py:15 fake.py:16 fake.py:17 fake.py:18 fake.py:19
 #: fake.py:20 fake.py:21 fake.py:22 fake.py:23 fake.py:24 fake.py:25 fake.py:26 fake.py:27 fake.py:28
 " work."
 msgstr ""
 
-""", buf.getvalue().lower())
+"""), buf.getvalue().lower())
 
     def test_pot_with_translator_comments(self):
         catalog = Catalog()
         catalog.add(u('bar'), locations=[('utils.py', 3)],
                     user_comments=['Comment About `bar` with',
                                    'multiple lines.'])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual('''#. Comment About `foo`
+        self.assertEqual(b('''#. Comment About `foo`
 #: main.py:1
 msgid "foo"
 msgstr ""
 # multiple lines.
 #: utils.py:3
 msgid "bar"
-msgstr ""''', buf.getvalue().strip())
+msgstr ""'''), buf.getvalue().strip())
 
     def test_po_with_obsolete_message(self):
         catalog = Catalog()
         catalog.obsolete['bar'] = Message(u('bar'), u('Bahr'),
                                           locations=[('utils.py', 3)],
                                           user_comments=['User comment'])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual('''#: main.py:1
+        self.assertEqual(b('''#: main.py:1
 msgid "foo"
 msgstr "Voh"
 
 # User comment
 #~ msgid "bar"
-#~ msgstr "Bahr"''', buf.getvalue().strip())
+#~ msgstr "Bahr"'''), buf.getvalue().strip())
 
     def test_po_with_multiline_obsolete_message(self):
         catalog = Catalog()
 """
         catalog.obsolete[msgid] = Message(msgid, msgstr,
                                           locations=[('utils.py', 3)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True)
-        self.assertEqual(r'''#: main.py:1
+        self.assertEqual(b(r'''#: main.py:1
 msgid "foo"
 msgstr "Voh"
 
 #~ msgstr ""
 #~ "Here's a message that covers\n"
 #~ "multiple lines, and should still be handled\n"
-#~ "correctly.\n"''', buf.getvalue().strip())
+#~ "correctly.\n"'''), buf.getvalue().strip())
 
     def test_po_with_obsolete_message_ignored(self):
         catalog = Catalog()
         catalog.obsolete['bar'] = Message(u('bar'), u('Bahr'),
                                           locations=[('utils.py', 3)],
                                           user_comments=['User comment'])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True, ignore_obsolete=True)
-        self.assertEqual('''#: main.py:1
+        self.assertEqual(b('''#: main.py:1
 msgid "foo"
-msgstr "Voh"''', buf.getvalue().strip())
+msgstr "Voh"'''), buf.getvalue().strip())
 
     def test_po_with_previous_msgid(self):
         catalog = Catalog()
         catalog.add(u('foo'), u('Voh'), locations=[('main.py', 1)],
                     previous_id=u('fo'))
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True, include_previous=True)
-        self.assertEqual('''#: main.py:1
+        self.assertEqual(b('''#: main.py:1
 #| msgid "fo"
 msgid "foo"
-msgstr "Voh"''', buf.getvalue().strip())
+msgstr "Voh"'''), buf.getvalue().strip())
 
     def test_po_with_previous_msgid_plural(self):
         catalog = Catalog()
         catalog.add((u('foo'), u('foos')), (u('Voh'), u('Voeh')),
                     locations=[('main.py', 1)], previous_id=(u('fo'), u('fos')))
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, omit_header=True, include_previous=True)
-        self.assertEqual('''#: main.py:1
+        self.assertEqual(b('''#: main.py:1
 #| msgid "fo"
 #| msgid_plural "fos"
 msgid "foo"
 msgid_plural "foos"
 msgstr[0] "Voh"
-msgstr[1] "Voeh"''', buf.getvalue().strip())
+msgstr[1] "Voeh"'''), buf.getvalue().strip())
 
     def test_sorted_po(self):
         catalog = Catalog()
                                    'multiple lines.'])
         catalog.add((u('foo'), u('foos')), (u('Voh'), u('Voeh')),
                     locations=[('main.py', 1)])
-        buf = StringIO()
+        buf = BytesIO()
         pofile.write_po(buf, catalog, sort_output=True)
         value = buf.getvalue().strip()
-        assert '''\
+        assert b('''\
 # Comment About `bar` with
 # multiple lines.
 #: utils.py:3
 msgid "foo"
 msgid_plural "foos"
 msgstr[0] "Voh"
-msgstr[1] "Voeh"''' in value
-        assert value.find('msgid ""') < value.find('msgid "bar"') < value.find('msgid "foo"')
+msgstr[1] "Voeh"''') in value
+        assert value.find(b('msgid ""')) < value.find(b('msgid "bar"')) < value.find(b('msgid "foo"'))
 
     def test_silent_location_fallback(self):
         buf = StringIO('''\

File babel/numbers.py

 except ImportError:
     have_decimal = False
 
-from babel.compat import u, b, long_type
+from babel.compat import u, b, long_type, PY3
 from babel.core import default_locale, Locale
 
 __all__ = ['format_number', 'format_decimal', 'format_currency',
     >>> parse_number('1.099', locale='de_DE') == long_type(1099)
     True
 
-    When the given string cannot be parsed, an exception is raised:
-    
-    >>> parse_number('1.099,98', locale='de')
-    Traceback (most recent call last):
-        ...
-    NumberFormatError: '1.099,98' is not a valid number
-    
+    When the given string cannot be parsed, a NumberFormatError is raised.
+
     :param string: the string to parse
     :param locale: the `Locale` object or locale identifier
     :return: the parsed number
     >>> parse_decimal('1.099,98', locale='de')
     1099.98
     
-    When the given string cannot be parsed, an exception is raised:
-    
-    >>> parse_decimal('2,109,998', locale='de')
-    Traceback (most recent call last):
-        ...
-    NumberFormatError: '2,109,998' is not a valid decimal number
+    When the given string cannot be parsed, a NumberFormatError is raised.
     
     :param string: the string to parse
     :param locale: the `Locale` object or locale identifier
             self.scale = 1
 
     def __repr__(self):
-        return '<%s %r>' % (type(self).__name__, self.pattern)
+        pattern = self.pattern.encode('ascii', 'backslashreplace').decode('utf-8')
+        return '<%s %s>' % (type(self).__name__, pattern)
 
     def apply(self, value, locale, currency=None):
         value *= self.scale
                 exp -= self.int_prec[0] - 1
             # Exponent grouping
             elif self.int_prec[1]:
-                exp = int(exp) / self.int_prec[1] * self.int_prec[1]
+                exp = int(exp) // self.int_prec[1] * self.int_prec[1]
             if not have_decimal or not isinstance(value, Decimal):
                 value = float(value)
             if exp < 0:

File babel/support.py

 import gettext
 import locale
 
-from babel.compat import text_type
+from babel.compat import text_type, u
 from babel.core import Locale
 from babel.dates import format_date, format_datetime, format_time, \
                         format_timedelta
     bound to a specific locale and time-zone.
     
     >>> fmt = Format('en_US', UTC)
-    >>> fmt.date(date(2007, 4, 1))
-    u'Apr 1, 2007'