BABEL(1)

๐Ÿ“ฆ
version 2.18.0 ยท 4 versions
, in fedora 43

BABEL(1) โ€” babel, bestie ๐Ÿ’…

ok so welcome to the babel manual page, section 1 of the Babel docs. grab a snack, we're going in ๐Ÿ“

โœจ NAME โœจ

babel โ€” Babel Documentation

Babel is an integrated collection of utilities that helps you internationalize and localize Python applications, with a big ol' emphasis on web-based applications. ๐Ÿฆ‹


๐Ÿ“š USER DOCUMENTATION ๐Ÿ“š

The user documentation walks you through some core concepts of the library and spills the tea on how you can actually use it.

๐ŸŒธ Introduction ๐ŸŒธ

The functionality Babel gives you for internationalization (I18n) and localization (L10N) splits into two whole different aspects, no cap:

  • tools to build and work with gettext message catalogs, and
  • a Python interface to the CLDR (Common Locale Data Repository), giving you access to various locale display names, localized number and date formatting, etc.

๐Ÿ’Œ Message Catalogs ๐Ÿ’Œ

So the Python standard library does include a gettext module that lets applications use message catalogs, yeah. But it requires developers to build those catalogs using GNU tools like xgettext, msgmerge, and msgfmt. And while xgettext does have support for extracting messages from Python files, it literally does not know how to deal with other kinds of files commonly found in Python web-applications, such as templates, and it doesn't give you an easy extensibility mechanism to add that support either. ๐Ÿ˜ญ

Babel addresses this by providing a framework where various extraction methods can be plugged in to a larger message extraction framework, and it also removes the dependency on the GNU gettext tools for common tasks, since those aren't necessarily available on all platforms. See Working with Message Catalogs for the deets on this aspect of Babel.

๐ŸŒ Locale Data ๐ŸŒ

Furthermore โ€” and this is the part that's giving main character energy โ€” while the Python standard library does include support for basic localization with respect to the formatting of numbers and dates (the locale module, among others), that support is based on the assumption that there will be only one specific locale used per process (at least simultaneously). Also, it doesn't provide access to other kinds of locale data, such as the localized names of countries, languages, or time-zones, which are frequently needed in web-based applications. ๐Ÿ™ˆ

For these requirements, Babel includes data extracted from the Common Locale Data Repository (CLDR), and provides a number of convenient methods for accessing and using this data. See Locale Data, Date and Time, and Number Formatting for more info on this aspect of Babel.

๐ŸŽ€ Installation ๐ŸŽ€

Babel is distributed as a standard Python package fully set up with all the dependencies it needs. On Python versions where the standard library zoneinfo module is not available, pytz needs to be installed for timezone support. If pytz is installed, it is preferred over the standard library zoneinfo module where possible. ๐Ÿ‘‘

๐Ÿงš virtualenv ๐Ÿงš

Virtualenv is probably what you want to use during development, and if you have shell access to your production machines, you'll probably want to use it there too. Use pip to install it:

$ sudo pip install virtualenv

If you're on Windows, run it in a command-prompt window with administrator privileges, and leave out sudo.

Once you have virtualenv installed, just fire up a shell and create your own environment. I usually create a project folder and a venv folder within:

$ mkdir myproject
$ cd myproject
$ virtualenv venv
New python executable in venv/bin/python
Installing distribute............done.

Now, whenever you want to work on a project, you only have to activate the corresponding environment. On OS X and Linux, do the following:

$ . venv/bin/activate

If you are a Windows user, the following command is for you:

$ venv\scripts\activate

Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). โœจ

Now you can just enter the following command to get Babel installed in your virtualenv:

$ pip install Babel

A few seconds later and you are good to go. ๐Ÿซถ

๐Ÿ’ป System-Wide Installation ๐Ÿ’ป

This is possible as well, though I do not recommend it. Just run pip with root privileges:

$ sudo pip install Babel

(On Windows systems, run it in a command-prompt window with administrator privileges, and leave out sudo.)

๐Ÿ”ฅ Living on the Edge ๐Ÿ”ฅ

If you want to work with the latest version of Babel, you will need to use a git checkout.

Get the git checkout in a new virtualenv and run in development mode:

$ git clone https://github.com/python-babel/babel
Initialized empty Git repository in ~/dev/babel/.git/
$ cd babel
$ virtualenv venv
New python executable in venv/bin/python
Installing distribute............done.
$ . venv/bin/activate
$ python setup.py import_cldr
$ pip install --editable .
...
Finished processing dependencies for Babel
Make sure to not forget about the import_cldr step, because otherwise you will be missing the locale data. The custom setup command will download the most appropriate CLDR release from the official website and convert it for Babel. ๐Ÿ˜ค

This will pull also in the dependencies and activate the git head as the current version inside the virtualenv. Then all you have to do is run git pull origin to update to the latest version. If the CLDR data changes you will have to re-run python setup.py import_cldr.

๐ŸŒˆ Locale Data ๐ŸŒˆ

While message catalogs allow you to localize any messages in your application, there are a number of strings that are used in many applications for which translations are readily available.

Imagine for example you have a list of countries that users can choose from, and you'd like to display the names of those countries in the language the user prefers. Instead of translating all those country names yourself in your application, you can make use of the translations provided by the locale data included with Babel, which is based on the Common Locale Data Repository (CLDR) developed and maintained by the Unicode Consortium. ๐ŸŒ

๐Ÿ‘‘ The Locale Class ๐Ÿ‘‘

You normally access such locale data through the Locale class provided by Babel:

>>> from babel import Locale
>>> locale = Locale('en', 'US')
>>> locale.territories['US']
'United States'
>>> locale = Locale('es', 'MX')
>>> locale.territories['US']
'Estados Unidos'

In addition to country/territory names, the locale data also provides access to names of languages, scripts, variants, time zones, and more. Some of the data is closely related to number and date formatting. ๐Ÿ’…

Most of the corresponding Locale properties return dictionaries, where the key is a code such as the ISO country and language codes. Consult the API documentation for references to the relevant specifications.

๐Ÿฆ‹ Likely Subtags ๐Ÿฆ‹

When dealing with locales you can run into the situation where a locale tag is not fully descriptive. For instance people commonly refer to zh_TW but that identifier does not resolve to a locale that the CLDR covers. Babel's locale identifier parser in that case will attempt to resolve the most likely subtag to end up with the intended locale:

>>> from babel import Locale
>>> Locale.parse('zh_TW')
Locale('zh', territory='TW', script='Hant')

This can also be used to find the most appropriate locale for a territory. In that case the territory code needs to be prefixed with und (unknown language identifier):

>>> Locale.parse('und_AZ')
Locale('az', territory='AZ', script='Latn')
>>> Locale.parse('und_DE')
Locale('de', territory='DE')
Babel currently cannot deal with fuzzy locales (a locale not fully backed by data files) so we only accept locales that are fully backed by CLDR data. This will change in the future, but for the time being this restriction is in place. ๐Ÿ’€

๐Ÿ’œ Locale Display Names ๐Ÿ’œ

Locales itself can be used to describe the locale itself or other locales. This mainly means that given a locale object you can ask it for its canonical display name, the name of the language and other things. Since the locales cross-reference each other you can ask for locale names in any language supported by the CLDR:

>>> l = Locale.parse('de_DE')
>>> l.get_display_name('en_US')
'German (Germany)'
>>> l.get_display_name('fr_FR')
'allemand (Allemagne)'

Display names include all the information to uniquely identify a locale (language, territory, script and variant) which is often not what you want. You can also ask for the information in parts:

>>> l.get_language_name('de_DE')
'Deutsch'
>>> l.get_language_name('it_IT')
'tedesco'
>>> l.get_territory_name('it_IT')
'Germania'
>>> l.get_territory_name('pt_PT')
'Alemanha'

๐ŸŽ€ Calendar Display Names ๐ŸŽ€

The Locale class provides access to many locale display names related to calendar display, such as the names of weekdays or months.

These display names are of course used for date formatting, but can also be used, for example, to show a list of months to the user in their preferred language:

>>> locale = Locale('es')
>>> month_names = locale.months['format']['wide'].items()
>>> for idx, name in sorted(month_names):
...     print name
enero
febrero
marzo
abril
mayo
junio
julio
agosto
septiembre
octubre
noviembre
diciembre

โญ Date and Time โญ

When working with date and time information in Python, you commonly use the classes date, datetime and/or time from the datetime package. Babel provides functions for locale-specific formatting of those objects in its dates module:

>>> from datetime import date, datetime, time
>>> from babel.dates import format_date, format_datetime, format_time

>>> d = date(2007, 4, 1)
>>> format_date(d, locale='en')
'Apr 1, 2007'
>>> format_date(d, locale='de_DE')
'01.04.2007'

As this example demonstrates, Babel will automatically choose a date format that is appropriate for the requested locale. ๐Ÿ˜Œ

The format_*() functions also accept an optional format argument, which allows you to choose between one of four format variations:

  • short,
  • medium (the default),
  • long, and
  • full.

For example:

>>> format_date(d, format='short', locale='en')
'4/1/07'
>>> format_date(d, format='long', locale='en')
'April 1, 2007'
>>> format_date(d, format='full', locale='en')
'Sunday, April 1, 2007'

๐Ÿ•ฐ๏ธ Core Time Concepts ๐Ÿ•ฐ๏ธ

okay bestie, real talk: dates and time are a whole mess and nobody's arguing about that. Babel gets it, so it justโ€ฆ makes some decisions for you and moves on. ๐Ÿ’…

So Python's datetime module has two whole vibes for dealing with times and dates: naive datetime objects and timezone-aware datetime objects. Two flavors, one drama. ๐Ÿ˜ญ

Babel's rec? store all your time in naive datetime objects and treat them as UTC at all times. Periodt. That simplifies everything so much, because the alternative is you ending up in the hairy situation where you're juggling datetime objects from a bunch of different timezones at once โ€” and that's tricky, fr fr, because there are literally situations where time itself is ambiguous. ๐Ÿ™ˆ Usually that happens with dates sitting around a timezone transition. And the most common timezone transition of all? The switch between daylight saving time and standard time. Every year, every time, chaos. ๐Ÿ’€

So: always use UTC internally, and only reformat to local time when you're handing dates back to the user. At that point the timezone your user picked can usually be figured out, and Babel can automatically rebase the time for you. iconic behaviour honestly. ๐Ÿ‘‘

To grab the current time, use the now() method of the datetime object, and pass utc to it as the timezone.

Want more tea on timezones? Go see Time-zone Support. ๐Ÿฆ‹

๐ŸŽ€ Pattern Syntax ๐ŸŽ€

Babel makes it stupid easy to use the right date/time format for whatever locale you're in, but you can also force it to use custom patterns if you're feeling main character. โœจ

Heads up though: Babel uses different patterns for specifying number and date formats than the Python equivalents do (think time.strftime() and friends), because those Python ones mostly got inherited from C and POSIX. Different lineage, different rules, iykyk.

The patterns Babel uses are based on the Locale Data Markup Language specification (LDML), and here's how that spec defines them:

A date/time pattern is a string of characters, where specific strings of characters are replaced with date and time data from a calendar when formatting or used to generate data for a calendar when parsing. [โ€ฆ] Characters may be used multiple times. For example, if y is used for the year, yy might produce "99", whereas yyyy produces "1999". For most numerical fields, the number of characters specifies the field width. For example, if h is the hour, h might produce "5", but hh produces "05". For some characters, the count specifies whether an abbreviated or full form should be used [โ€ฆ] Two single quotes represent a literal single quote, either inside or outside single quotes. Text within single quotes is not interpreted in any way (except for two adjacent single quotes).

Like, for example:

>>> d = date(2007, 4, 1)
>>> format_date(d, "EEE, MMM d, ''yy", locale='en')
"Sun, Apr 1, '07"
>>> format_date(d, "EEEE, d.M.yyyy", locale='de')
'Sonntag, 1.4.2007'

>>> t = time(15, 30)
>>> format_time(t, "hh 'o''clock' a", locale='en')
"03 o'clock PM"
>>> format_time(t, 'H:mm a', locale='de')
'15:30 nachm.'

>>> dt = datetime(2007, 4, 1, 15, 30)
>>> format_datetime(dt, "yyyyy.MMMM.dd GGG hh:mm a", locale='en')
'02007.April.01 AD 03:30 PM'

See? ate. ๐Ÿ’–

The syntax for custom datetime format patterns is described in detail in the Locale Data Markup Language specification. The table that follows is just a relatively brief overview, so don't come for it if it doesn't cover your niche little edge case. ๐ŸŒธ

โœจ Date Fields โœจ

okay bestie, this is the part where babel lets you format, like, dates โ€” and there are a LOT of little symbols, so grab a snack ๐Ÿ“. Each one is a letter you drop into your format string, and the number of times you repeat it changes what you get back. No cap.

  • Era ๐ŸŒ…
    • G โ€” Replaced with the era string for the current date. One to three letters for the abbreviated form, four letters for the long form, five for the narrow form.
  • Year ๐Ÿ“…
    • y โ€” Replaced by the year. Normally the length specifies the padding, but for two letters it also specifies the maximum length.
    • Y โ€” Same as y but uses the ISO year-week calendar. ISO year-week increments after completing the last week of the year. Therefore it may change a few days before or after y. Recommend use with the w Symbol.
    • u โ€” ??
  • Quarter ๐Ÿ—“๏ธ
    • Q โ€” Use one or two for the numerical quarter, three for the abbreviation, or four for the full name.
    • q โ€” Use one or two for the numerical quarter, three for the abbreviation, or four for the full name.
  • Month ๐ŸŒธ
    • M โ€” Use one or two for the numerical month, three for the abbreviation, or four for the full name, or five for the narrow name.
    • L โ€” Use one or two for the numerical month, three for the abbreviation, or four for the full name, or 5 for the narrow name.
  • Week ๐Ÿ’œ
    • w โ€” Week of year according to the ISO year-week calendar. This may have 52 or 53 weeks depending on the year. Recommend use with the Y symbol.
    • W โ€” Week of month.
  • Day โญ
    • d โ€” Day of month.
    • D โ€” Day of year.
    • F โ€” Day of week in month.
    • g โ€” ??
  • Week day ๐ŸŽ€
    • E โ€” Day of week. Use one through three letters for the short day, or four for the full name, or five for the narrow name.
    • e โ€” Local day of week. Same as E except adds a numeric value that will depend on the local starting day of the week, using one or two letters.
    • c โ€” ??
not the ?? entries just vibing there with zero explanation ๐Ÿ’€ iykyk, we report what the page says, and the page says ??. iconic of it honestly.

โฐ Time Fields โฐ

Hereโ€™s the symbol tea, straight from the table, bestie. These are the little letters that go inside date/time format patterns, and they tell Babel which piece of time youโ€™re talking about. No cap, youโ€™re gonna want these memorized ๐Ÿ’…

  • a โ€” Period: AM or PM.
  • h โ€” Hour [1-12].
  • H โ€” Hour [0-23].
  • K โ€” Hour [0-11].
  • k โ€” Hour [1-24].
  • m โ€” Minute. Use one or two for zero places padding.
  • s โ€” Second. Use one or two for zero places padding.
  • S โ€” Fractional second, rounds to the count of letters.
  • A โ€” Milliseconds in day.
  • z โ€” Timezone. Use one to three letters for the short timezone or four for the full name.
  • Z โ€” Use one to three letters for RFC 822, four letters for GMT format.
  • v โ€” Use one letter for short wall (generic) time, four for long wall time.
  • V โ€” Same as z, except that timezone abbreviations should be used regardless of whether they are in common use by the locale.

๐Ÿ•ฐ๏ธ Time Delta Formatting ๐Ÿ•ฐ๏ธ

In addition to providing functions for formatting localized dates and times, the babel.dates module also provides a function to format the difference between two times, called a โ€˜โ€™time deltaโ€™โ€™. These are usually represented as datetime.timedelta objects in Python, and itโ€™s also what you get when you subtract one datetime object from an other.

The format_timedelta function takes a timedelta object and returns a human-readable representation. This happens at the cost of precision, as it chooses only the most significant unit (such as year, week, or hour) of the difference, and displays that:

>>> from datetime import timedelta
>>> from babel.dates import format_timedelta
>>> delta = timedelta(days=6)
>>> format_timedelta(delta, locale='en_US')
'1 week'

The resulting strings are based from the CLDR data, and are properly pluralized depending on the plural rules of the locale and the calculated number of units.

The function provides parameters for you to influence how this most significant unit is chosen: with threshold you set the value after which the presentation switches to the next larger unit, and with granularity you can limit the smallest unit to display:

>>> delta = timedelta(days=6)
>>> format_timedelta(delta, threshold=1.2, locale='en_US')
'6 days'
>>> format_timedelta(delta, granularity='month', locale='en_US')
'1 month'

๐ŸŒ Time-zone Support ๐ŸŒ

Many of the verbose time formats include the time-zone, but time-zone information is not by default available for the Python datetime and time objects. The standard library includes only the abstract tzinfo class, which you need appropriate implementations for to actually use in your application. Babel includes a tzinfo implementation for UTC (Universal Time).

Babel uses either zoneinfo_ or pytz for timezone support. If pytz is installed, it is preferred over the standard libraryโ€™s zoneinfo. You can directly interface with either of these modules from within Babel:

>>> from datetime import time
>>> from babel.dates import get_timezone, UTC
>>> dt = datetime(2007, 4, 1, 15, 30, tzinfo=UTC)
>>> eastern = get_timezone('US/Eastern')
>>> format_datetime(dt, 'H:mm Z', tzinfo=eastern, locale='en_US')
'11:30 -0400'

The recommended approach to deal with different time-zones in a Python application is to always use UTC internally, and only convert from/to the users time-zone when accepting user input and displaying date/time data, respectively. You can use Babel together with zoneinfo or pytz to apply a time-zone to any datetime or time object for display, leaving the original information unchanged:

>>> british = get_timezone('Europe/London')
>>> format_datetime(dt, 'H:mm zzzz', tzinfo=british, locale='en_US')
'16:30 British Summer Time'

Here, the given UTC time is adjusted to the โ€œEurope/Londonโ€ time-zone, and daylight savings time is taken into account. Daylight savings time is also applied to format_time, but because the actual date is unknown in that case, the current day is assumed to determine whether DST or standard time should be used.

Babel also provides support for working with the local timezone of your operating system. Itโ€™s provided through the LOCALTZ constant:

>>> from babel.dates import LOCALTZ, get_timezone_name
>>> LOCALTZ
<DstTzInfo 'Europe/Vienna' CET+1:00:00 STD>
>>> get_timezone_name(LOCALTZ)
'Central European Time'

๐Ÿ—บ๏ธ Localized Time-zone Names ๐Ÿ—บ๏ธ

While the Locale class provides access to various locale display names related to time-zones, the process of building a localized name of a time-zone is actually quite complicated. Babel implements it in separately usable functions in the babel.dates module, most importantly the get_timezone_name function:

>>> from babel import Locale
>>> from babel.dates import get_timezone_name, get_timezone

>>> tz = get_timezone('Europe/Berlin')
>>> get_timezone_name(tz, locale=Locale.parse('pt_PT'))
'Hora da Europa Central'

You can pass the function either a datetime.tzinfo object, or a datetime.date or datetime.datetime object. If you pass an actual date, the function will be able to take daylight savings time into account. If you pass just the time-zone, Babel does not know whether daylight savings time is in effect, so it uses a generic representation, which is useful for example to display a list of time-zones to the user.

>>> from datetime import datetime
>>> from babel.dates import _localize

>>> dt = _localize(tz, datetime(2007, 8, 15))
>>> get_timezone_name(dt, locale=Locale.parse('de_DE'))
'Mitteleuropรคische Sommerzeit'
>>> get_timezone_name(tz, locale=Locale.parse('de_DE'))
'Mitteleuropรคische Zeit'

๐Ÿ”ข Number Formatting ๐Ÿ”ข

Support for locale-specific formatting and parsing of numbers is provided by the babel.numbers module:

>>> from babel.numbers import format_number, format_decimal, format_compact_decimal, format_percent

Examples:

# Numbers with decimal places
>>> format_decimal(1.2345, locale='en_US')
'1.234'
>>> format_decimal(1.2345, locale='sv_SE')
'1,234'
# Integers with thousand grouping
>>> format_decimal(12345, locale='de_DE')
'12.345'
>>> format_decimal(12345678, locale='de_DE')
'12.345.678'

โœจ Pattern Syntax โœจ

Okay so Babel is out here making it super easy to grab the right number format for whatever locale you're in, but bestie, you can ALSO force it to use your own custom patterns. And literally same energy as the date/time formatting patterns โ€” the ones Babel supports for number formatting are based on the Locale Data Markup Language specification (LDML). ๐Ÿ’…

Examples, because obviously:

>>> format_decimal(-1.2345, format='#,##0.##;-#', locale='en')
'-1.23'
>>> format_decimal(-1.2345, format='#,##0.##;(#)', locale='en')
'(1.23)'

The syntax for custom number format patterns is described in detail in the spec. The following is just a relatively brief overview, no cap:

  • 0 โ€” Digit
  • 1-9 โ€” โ€˜1โ€™ through โ€˜9โ€™ indicate rounding.
  • @ โ€” Significant digit
  • # โ€” Digit, zero shows as absent
  • . โ€” Decimal separator or monetary decimal separator
  • - โ€” Minus sign
  • , โ€” Grouping separator
  • E โ€” Separates mantissa and exponent in scientific notation
  • + โ€” Prefix positive exponents with localized plus sign
  • ; โ€” Separates positive and negative subpatterns
  • % โ€” Multiply by 100 and show as percentage
  • โ€ฐ โ€” Multiply by 1000 and show as per mille
  • ยค โ€” Currency sign, replaced by currency symbol. If doubled, replaced by international currency symbol. If tripled, uses the long form of the decimal symbol.
  • ' โ€” Used to quote special characters in a prefix or suffix
  • * โ€” Pad escape, precedes pad character

๐ŸŒธ Rounding Modes ๐ŸŒธ

So the tea is: Babel makes full use of Python's Decimal type to do number rounding before formatting, which means you, the user, get to control the rounding mode and other configurable parameters through the active Context instance. Iconic behaviour honestly. ๐Ÿ‘‘

By default, Python rounding mode is ROUND_HALF_EVEN, which complies with UTS #35 section 3.3. But the caller has the opportunity to tweak the current context before formatting a number or currency:

>>> from babel.numbers import decimal, format_decimal
>>> with decimal.localcontext(decimal.Context(rounding=decimal.ROUND_DOWN)):
>>>    txt = format_decimal(123.99, format='#', locale='en_US')
>>> txt
'123'

It is also possible to use decimal.setcontext or directly modifying the instance returned by decimal.getcontext. However, using a context manager is always more convenient due to the automatic restoration and the ability to nest them. Nesting?? The versatility. ๐Ÿฆ‹

Whatever mechanism is chosen, always make use of the decimal module imported from babel.numbers. For efficiency reasons, Babel uses the fastest decimal implementation available, such as cdecimal. These various implementations offer an identical API, but their types and instances do not interoperate with each other. ๐Ÿ’€

For example, the previous example can be slightly modified to generate unexpected results on Python 2.7, with the cdecimal module installed:

>>> from decimal import localcontext, Context, ROUND_DOWN
>>> from babel.numbers import format_decimal
>>> with localcontext(Context(rounding=ROUND_DOWN)):
>>>    txt = format_decimal(123.99, format='#', locale='en_US')
>>> txt
'124'

Changing other parameters such as the precision may also alter the results of the number formatting functions. Remember to test your code to make sure it behaves as desired. (Not me telling you to test your code, but like, test your code.) ๐Ÿซถ


๐ŸŽ€ Parsing Numbers ๐ŸŽ€

Babel can also parse numeric data in a locale-sensitive manner:

>>> from babel.numbers import parse_decimal, parse_number

Examples, bestie:

>>> parse_decimal('1,099.98', locale='en_US')
1099.98
>>> parse_decimal('1.099,98', locale='de')
1099.98
>>> parse_decimal('2,109,998', locale='de')
Traceback (most recent call last):
  ...
NumberFormatError: '2,109,998' is not a valid decimal number
Note: as of version 2.8.0, the parse_number function has limited functionality. It can remove group symbols of certain locales from numeric strings, but may behave unexpectedly until its logic handles more encoding issues and other special cases. So likeโ€ฆ heads up. ๐Ÿ™ˆ

Examples:

>>> parse_number('1,099', locale='en_US')
1099
>>> parse_number('1.099.024', locale='de')
1099024
>>> parse_number('123' + '\xa0' + '4567', locale='ru')
1234567
>>> parse_number('123 4567', locale='ru')
  ...
NumberFormatError: '123 4567' is not a valid number

๐Ÿ’– Working with Message Catalogs ๐Ÿ’–

๐ŸŒˆ Introduction ๐ŸŒˆ

The gettext translation system enables you to mark any strings used in your application as subject to localization, by wrapping them in functions such as gettext(str) and ngettext(singular, plural, num). For brevity, the gettext function is often aliased to _(str), so you can write:

print(_("Hello"))

instead of just:

print("Hello")

to make the string โ€œHelloโ€ localizable. The way that's so simple fr fr. โœจ

Message catalogs are collections of translations for such localizable messages used in an application. They are commonly stored in PO (Portable Object) and MO (Machine Object) files, the formats of which are defined by the GNU gettext tools and the GNU translation project.

The general procedure for building message catalogs looks something like this:

  • use a tool (such as xgettext) to extract localizable strings from the code base and write them to a POT (PO Template) file.
  • make a copy of the POT file for a specific locale (for example, โ€œen_USโ€) and start translating the messages
  • use a tool such as msgfmt to compile the locale PO file into a binary MO file
  • later, when code changes make it necessary to update the translations, you regenerate the POT file and merge the changes into the various locale-specific PO files, for example using msgmerge

Python provides the gettext module as part of the standard library, which enables applications to work with appropriately generated MO files.

As gettext provides a solid and well supported foundation for translating application messages, Babel does not reinvent the wheel, but rather reuses this infrastructure, and makes it easier to build message catalogs for Python applications. The humility!! The reusing of perfectly good infrastructure!! We love to see it. ๐ŸŽ€


๐Ÿงš Message Extraction ๐Ÿงš

Babel provides functionality similar to that of the xgettext program, except that only extraction from Python source files is built-in, while support for other file formats can be added using a simple extension mechanism.

Unlike xgettext, which is usually invoked once for every file, the routines for message extraction in Babel operate on directories. While the per-file approach of xgettext works nicely with projects using a Makefile, Python projects rarely use make, and thus a different mechanism is needed for extracting messages from the heterogeneous collection of source files that many Python projects are composed of. Directory brain, honestly. ๐Ÿฆ‹

...so yeah, when message extraction works off directories instead of one-file-at-a-time, you need a way to tell Babel which files get treated how. ๐Ÿซถ Like โ€” a project can have a million .html files and they are not all the same vibe: some are plain static HTML with zero localizable messages, some are Jinja2 templates, some are Genshi markup templates, and some chaotic projects (for whatever reason ๐Ÿ’€) mix HTML files from different template languages in the same repo. So the way messages get extracted from a source file can't just hinge on the file extension โ€” it has to be controllable, like, precisely.

Babel takes a configuration file to specify this mapping of files โ†’ extraction methods, and bestie, that's exactly what we're getting into below. ๐Ÿ’–


๐ŸŒธ Front-Ends ๐ŸŒธ

Babel gives you two different front-ends for working with message catalogs:

  • A Command-Line Interface, and
  • Distutils/Setuptools Integration

Which one you pick is honestly about the nature of your project. For most modern Python projects, the distutils/setuptools integration is probably the more convenient glow-up. โœจ


๐ŸŽ€ Extraction Method Mapping and Configuration ๐ŸŽ€

The mapping of extraction methods to files in Babel happens through a config file. That file maps extended glob patterns to the names of extraction methods, and it can also set various options for each pattern (which options exist depends entirely on the specific extraction method you're using, iykyk).

For example, this config adds message extraction from both Genshi markup templates and text templates:

# Extraction from Python source files

[python: **.py]

# Extraction from Genshi HTML and text templates

[genshi: **/templates/**.html]
ignore_tags = script,style
include_attrs = alt title summary

[genshi: **/templates/**.txt]
template_class = genshi.template:TextTemplate
encoding = ISO-8819-15

# Extraction from JavaScript files

[javascript: **.js]
extract_messages = $._, jQuery._

The extended glob patterns in this config are similar to the glob patterns most shells hand you. A single asterisk (*) is a wildcard for any number of characters โ€” except for the pathname component separator / โ€” while a question mark (?) matches only a single character. And then, plot twist: two subsequent asterisk characters (**) can be used to make the wildcard match any directory level, so the pattern **.txt matches any file with the extension .txt in any directory. ๐Ÿ’…

Babel supports two configuration file formats: INI and TOML.


๐Ÿฆ‹ INI Configuration Format ๐Ÿฆ‹

The INI config file syntax is based on the format you commonly find in .INI files on Windows systems, and as supported by the ConfigParser module in the Python standard library. Section names โ€” those strings wrapped in square brackets โ€” specify both the name of the extraction method and the extended glob pattern for the files that extraction method should be used on, separated by a colon. The options inside the sections get passed to the extraction method. Which options are available is specific to the extraction method you're using, no cap.

Lines that start with a # or ; character are ignored and can be used for comments. Empty lines are ignored too. โœจ


๐ŸŒˆ TOML Configuration Format ๐ŸŒˆ

Babel also supports TOML format for config files โ€” when the tomllib module is available (Python 3.11+), or when the tomli package is installed (for Python versions prior to 3.11).

TOML gives you a more structured format and is particularly useful when combined with pyproject.toml. Chef's kiss. ๐Ÿ‘‘

The same configuration examples shown above can be written in TOML format:

# Extraction from Python source files
[[mappings]]
method = "python"
pattern = "**.py"

# Extraction from Genshi HTML and text templates
[[mappings]]
method = "genshi"
pattern = "**/templates/**.html"
ignore_tags = "script,style"
include_attrs = "alt title summary"

[[mappings]]
method = "genshi"
pattern = "**/templates/**.txt"
template_class = "genshi.template:TextTemplate"
encoding = "ISO-8819-15"

# Extraction from JavaScript files
[[mappings]]
method = "javascript"
pattern = "**.js"
extract_messages = "$._, jQuery._"

In TOML format, each [[mappings]] section defines a mapping. The method and pattern fields are required. The pattern field can be a string or an array of strings, so you can match multiple patterns with the same configuration. ๐Ÿซถ

If you're using pyproject.toml, nest the configuration under [tool.babel]:

[tool.babel]
[[tool.babel.mappings]]
method = "python"
pattern = "**.py"

You can reference custom extractors in both formats. In TOML:

[extractors]
custom = "mypackage.module:extract_custom"

[[mappings]]
method = "custom"
pattern = "**.ctm"
some_option = "foo"

โœจ Common Options โœจ

On top of extractor-specific options, these following options can be specified in any mapping section, and they'll be merged with global settings:

  • keywords โ€” a list of keywords (function names) to extract messages from. This uses the same syntax as the --keyword command-line option. Keywords specified here are added to (not replacing!) the default keywords or those specified via command-line.

In INI format, whitespace-separated: keywords = _ gettext ngettext:1,2 pgettext:1c,2

In TOML format, use either a whitespace-separated string or an array: keywords = "_ gettext ngettext:1,2" or keywords = ["_", "gettext", "ngettext:1,2"]

  • add_comments โ€” a list of comment tag prefixes to extract and include in the output. This uses the same syntax as the --add-comments command-line option. Comment tags specified here are added to those specified via command-line.

In INI format, whitespace-separated: add_comments = TRANSLATOR: NOTE:

In TOML format, use either a string or an array: add_comments = "TRANSLATOR NOTE:" (parsed as a single string!) or add_comments = ["TRANSLATOR:", "NOTE:"]

Example in INI format:

[python: **.py]
keywords = _ _l _n:1,2
add_comments = TRANSLATOR:

Example in TOML format:

[[mappings]]
method = "python"
pattern = "**.py"
keywords = ["_", "_l", "_n:1,2"]
add_comments = ["TRANSLATOR:"]
NOTE: if you're performing message extraction using the command Babel provides for integration into setup.py scripts, you can also provide this configuration a different way โ€” namely as a keyword argument to the setup() function. See Distutils/Setuptools Integration for more information. ๐Ÿ’œ

๐Ÿ’– Default Extraction Methods ๐Ÿ’–

Babel ships with a few builtin extractors: python (which extracts messages from Python source files), javascript, and ignore (which extracts nothing, iconic honestly).

The python extractor is by default mapped to the glob pattern **.py, meaning it'll be applied to all files with the .py extension in any directory. But heads up bestie: if you specify your own mapping configuration, this default mapping is discarded, so you need to explicitly add it to your mapping (like the example above does). ๐Ÿ˜ญ


๐Ÿงš Referencing Extraction Methods ๐Ÿงš

Generally, packages publish Babel extractors as Python entry points, so you can just use a short name like "genshi" to refer to them.

If the package implementing an extraction method hasn't been installed in a way that kept its entry point mapping intact, you need to map the short names to fully qualified function names in an extract section in the mapping configuration. For example:

# Some custom extraction method

[extractors]
custom = mypackage.module:extract_custom

[custom: **.ctm]
some_option = foo

Note that the builtin extraction methods python and ignore are always available, and you should never need to explicitly define them in the [extractors] section. ๐Ÿ’…


๐Ÿ“ Writing Extraction Methods ๐Ÿ“

Adding new methods for extracting localizable messages is easy. First, you'll need to implement a function that complies with the following interface:

def extract_xxx(fileobj, keywords, comment_tags, options):
    """Extract messages from XXX files.

    :param fileobj: the file-like object the messages should be extracted
                    from
    :param keywords: a list of keywords (i.e. function names) that should
                     be recognized as translation functions
    :param comment_tags: a list of translator tags to search for and
                         include in the results
    :param options: a dictionary of additional options (optional)
    :return: an iterator over ``(lineno, funcname, message, comments)``
             tuples
    :rtype: ``iterator``
    """
NOTE: any strings in the tuples produced by this function must be either unicode objects, or str objects using plain ASCII characters. That means if sources contain strings using other encodings, it is the job of the extractor implementation to do the decoding to unicode objects. ๐Ÿซก

Next, you should register that function as an entry point_ . If using setup.py, add something like the following to your setup.py script:

def setup(...

    entry_points = """
    [babel.extractors]
    xxx = your.package:extract_xxx
    """,

If using a pyproject.toml file, add something like the following:

[project.entry-points."babel.extractors"]
xxx = "your.package:extract_xxx"

That is, add your extraction method to the entry point group babel.extractors, where the name of the entry point is the name that people will use to reference the extraction method, and the value being the module and the name of the function (separated by a colon) implementing the actual extraction. Slay. ๐ŸŒŸ

NOTE: as shown in Referencing Extraction Methods, declaring an entry point is not strictly required, since users can still reference the extraction function directly. But whenever possible, the entry point should be declared to make configuration more convenient. ๐Ÿ’–

๐ŸŒธ Translator Comments ๐ŸŒธ

First of all, what even are comment tags? Comment tags are excerpts of text to search for in comments โ€” only comments โ€” right before the python gettext calls, as shown in the following example:

# NOTE: This is a comment about `Foo Bar`
_('Foo Bar')

The comments tag for the above example would be NOTE:, and the translator comment for that tag would be This is a comment about Foo Bar``.

The resulting output in the catalog template would be something like:

#. This is a comment about `Foo Bar`
#: main.py:2
msgid "Foo Bar"
msgstr ""

And now you're probably like "bestie why would I ever need that??" โ€” valid question, love that for you, let's talk about it. ๐Ÿ’…

Picture the simplest possible scenario: you've got a menu item called "manual". You know exactly what it means. But the translator opens the file and is just standing there like ๐Ÿง wondering โ€” did you mean:

  1. a document or help manual, or
  2. a manual process?

This is genuinely the simplest case where a translation comment like "The installation manual" swoops in, clarifies everything, and makes a translator way more productive. Iconic behaviour, honestly.

NOTE: whether translator comments can even be extracted depends on the extraction method in use. The Python extractor provided by Babel does implement this feature, but others may not. So like... don't assume, okay? ๐Ÿ™ˆ

โœจ Command-Line Interface โœจ

Babel ships with a command-line interface for working with message catalogs, similar to the various GNU gettext tools that are commonly hanging out on Linux/Unix systems. ๐Ÿง

When it's properly installed, Babel gives you a script called pybabel:

$ pybabel --help
Usage: pybabel command [options] [args]

Options:
  --version       show program's version number and exit
  -h, --help      show this help message and exit
  --list-locales  print all known locales and exit
  -v, --verbose   print as much as possible
  -q, --quiet     print as little as possible

commands:
  compile  compile message catalogs to MO files
  extract  extract messages from source files and generate a POT file
  init     create new message catalogs from a POT file
  update   update existing message catalogs from a POT file

The pybabel script is basically a whole squad of sub-commands that do the actual work. Those sub-commands? Described below, babes. ๐Ÿ‘‡


๐Ÿ’– compile ๐Ÿ’–

The compile sub-command is your girl for compiling translation catalogs into binary MO files:

$ pybabel compile --help
Usage: pybabel compile [options]

compile message catalogs to MO files

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domains of PO files (space separated list, default
                        'messages')
  -d DIRECTORY, --directory=DIRECTORY
                        path to base directory containing the catalogs
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.mo')
  -l LOCALE, --locale=LOCALE
                        locale of the catalog to compile
  -f, --use-fuzzy       also include fuzzy translations
  --statistics          print statistics about translations

If directory is specified but output-file isn't, the default filename of the output file will be:

<directory>/<locale>/LC_MESSAGES/<domain>.mo

And if neither the input_file nor the locale option is set? This command goes looking for all the catalog files in the base directory that match the given domain, and compiles every single one of them into MO files in the same directory. She's thorough like that. โœจ


๐Ÿ”ฅ extract ๐Ÿ”ฅ

The extract sub-command pulls localizable messages out of a collection of source files:

$ pybabel extract --help
Usage: pybabel extract [options] <input-paths>

extract messages from source files and generate a POT file

Options:
  -h, --help            show this help message and exit
  --charset=CHARSET     charset to use in the output file (default "utf-8")
  -k KEYWORDS, --keywords=KEYWORDS, --keyword=KEYWORDS
                        space-separated list of keywords to look for in
                        addition to the defaults (may be repeated multiple
                        times)
  --no-default-keywords
                        do not include the default keywords
  -F MAPPING_FILE, --mapping-file=MAPPING_FILE, --mapping=MAPPING_FILE
                        path to the mapping configuration file
  --no-location         do not include location comments with filename and
                        line number
  --add-location=ADD_LOCATION
                        location lines format. If it is not given or "full",
                        it generates the lines with both file name and line
                        number. If it is "file", the line number part is
                        omitted. If it is "never", it completely suppresses
                        the lines (same as --no-location).
  --omit-header         do not include msgid "" entry in header
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE, --output=OUTPUT_FILE
                        name of the output file
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines
  --sort-output         generate sorted output (default False)
  --sort-by-file        sort output by file location (default False)
  --msgid-bugs-address=MSGID_BUGS_ADDRESS
                        set report address for msgid
  --copyright-holder=COPYRIGHT_HOLDER
                        set copyright holder in output
  --project=PROJECT     set project name in output
  --version=VERSION     set project version in output
  -c ADD_COMMENTS, --add-comments=ADD_COMMENTS
                        place comment block with TAG (or those preceding
                        keyword lines) in output file. Separate multiple TAGs
                        with commas(,)
  -s, --strip-comments, --strip-comment-tags
                        strip the comment TAGs from the comments.
  --input-dirs=INPUT_DIRS
                        alias for input-paths (does allow files as well as
                        directories).
  --ignore-dirs=IGNORE_DIRS
                        Patterns for directories to ignore when scanning for
                        messages. Separate multiple patterns with spaces
                        (default ".* ._")
  --header-comment=HEADER_COMMENT
                        header comment for the catalog

Okay so the meaning of the --keyword values is honestly kind of a whole thing, so let's break it down, bestie:

  • Pass a simple identifier like _ and it'll extract the first (and only the first) argument of all function calls to _.
  • Wanna grab arguments that aren't the first one? Add a colon and then the argument indices separated by commas. For example, the dngettext function typically expects translatable strings as its second and third arguments, so you'd pass dngettext:2,3.
  • Some arguments aren't translatable strings at all โ€” they're context strings. For those, append "c" to the argument index. For example: pgettext:1c,2.
  • In C++ and Python, you might have functions that behave differently depending on how many arguments they take. For this use case you can add an integer followed by "t" after the colon. When you do that, the keyword will only match a function invocation if it has that specified total number of arguments. For example, if you've got a function foo that acts like gettext (the argument is a message) or like pgettext (the arguments are a context and a message) depending on whether it takes one or two arguments, you can pass --keyword=foo:1,1t --keyword=foo:1c,2,2t.

The default keywords are equivalent to passing:

--keyword=_
--keyword=gettext
--keyword=ngettext:1,2
--keyword=ugettext
--keyword=ungettext:1,2
--keyword=dgettext:2
--keyword=dngettext:2,3
--keyword=N_
--keyword=pgettext:1c,2
--keyword=npgettext:1c,2,3

๐ŸŽ€ init ๐ŸŽ€

The init sub-command creates a brand new translations catalog based on a PO template file:

$ pybabel init --help
Usage: pybabel init [options]

create new message catalogs from a POT file

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domain of PO file (default 'messages')
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -d OUTPUT_DIR, --output-dir=OUTPUT_DIR
                        path to output directory
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.po')
  -l LOCALE, --locale=LOCALE
                        locale for the new localized catalog
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines

๐Ÿฆ‹ update ๐Ÿฆ‹

The update sub-command updates an existing new translations catalog based on a PO template file:

$ pybabel update --help
Usage: pybabel update [options]

update existing message catalogs from a POT file

Options:
  -h, --help            show this help message and exit
  -D DOMAIN, --domain=DOMAIN
                        domain of PO file (default 'messages')
  -i INPUT_FILE, --input-file=INPUT_FILE
                        name of the input file
  -d OUTPUT_DIR, --output-dir=OUTPUT_DIR
                        path to base directory containing the catalogs
  -o OUTPUT_FILE, --output-file=OUTPUT_FILE
                        name of the output file (default
                        '<output_dir>/<locale>/LC_MESSAGES/<domain>.po')
  --omit-header         do not include msgid  entry in header
  -l LOCALE, --locale=LOCALE
                        locale of the catalog to compile
  -w WIDTH, --width=WIDTH
                        set output line width (default 76)
  --no-wrap             do not break long message lines, longer than the
                        output line width, into several lines
  --ignore-obsolete     whether to omit obsolete messages from the output
  --init-missing        if any output files are missing, initialize them first
  -N, --no-fuzzy-matching
                        do not use fuzzy matching
  --update-header-comment
                        update target header comment
  --previous            keep previous msgids of translated messages

If output_dir is specified but output-file isn't, the default filename of the output file will be:

<directory>/<locale>/LC_MESSAGES/<domain>.mo

If neither the output_file nor the locale option is set, this command goes on a lil scavenger hunt ๐Ÿ” โ€” it looks for all the catalog files in the base directory that match the given domain, and updates every single one of them. All of them. No catalog left behind. ๐Ÿ’–


โœจ Distutils/Setuptools Integration โœจ

Babel ships commands you can plug straight into your setup.py scripts, built on either the distutils package (that's the one chillin' in the Python standard library) or the third-party setuptools package. Bestie, you get options. ๐ŸŽ€

These commands are available by default when Babel has been properly installed and your setup.py is using setuptools. But for projects still running plain old distutils? You gotta register the commands explicitly, like this:

from distutils.core import setup
from babel.messages import frontend as babel

setup(
    ...
    cmdclass = {'compile_catalog': babel.compile_catalog,
                'extract_messages': babel.extract_messages,
                'init_catalog': babel.init_catalog,
                'update_catalog': babel.update_catalog}
)

Look at that cmdclass dict. So neat. So tidy. ๐Ÿซถ


๐Ÿ› ๏ธ compile_catalog

The compile_catalog command is giving major GNU msgfmt energy โ€” it takes a message catalog from a PO file and compiles it into a binary MO file. Glow-up complete. โœจ

If the command has been correctly installed or registered, your project's setup.py script should let you use it like this:

$ ./setup.py compile_catalog --help
Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message

Options for 'compile_catalog' command:
   ...

And running the command will produce a binary MO file, just like that:

$ ./setup.py compile_catalog --directory foobar/locale --locale pt_BR
running compile_catalog
compiling catalog to foobar/locale/pt_BR/LC_MESSAGES/messages.mo

Manifesting a compiled catalog into existence. ๐ŸŒˆ


๐ŸŒธ Options

The compile_catalog command accepts the following options:

  • --domain โ€” domain of the PO file (defaults to the lower-cased project name)
  • --directory (-d) โ€” name of the base directory
  • --input-file (-i) โ€” name of the input file
  • --output-file (-o) โ€” name of the output file
  • --locale (-l) โ€” locale for the new localized string
  • --use-fuzzy (-f) โ€” also include โ€œfuzzyโ€ translations
  • --statistics โ€” print statistics about translations

If directory is specified, but output-file is not, the default filename of the output file will be:

<directory>/<locale>/LC_MESSAGES/<domain>.mo

If neither the input_file nor the locale option is set, this command looks for all catalog files in the base directory that match the given domain, and compiles each of them to MO files in the same directory. Mass compiling. Iconic behaviour honestly. ๐Ÿ’…

These options can either be specified on the command-line, or in the setup.cfg file. Your call, queen. ๐Ÿ‘‘


๐Ÿฆ‹ extract_messages

The extract_messages command is basically the GNU xgettext program's cooler cousin: it can extract localizable messages from a variety of different source files, and generate a PO (portable object) template file from all the messages it collected. Shopping spree but for strings. ๐Ÿ›๏ธ

If the command has been correctly installed or registered, a project's setup.py script should allow you to use the command:

$ ./setup.py extract_messages --help
Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message

Options for 'extract_messages' command:
   ...

Running the command will produce a PO template file:

$ ./setup.py extract_messages --output-file foobar/locale/messages.pot
running extract_messages
extracting messages from foobar/__init__.py
extracting messages from foobar/core.py
...
writing PO template file to foobar/locale/messages.pot

Look at it go, extracting from every file, one by one. Obsessed. ๐Ÿ’š


๐Ÿงš Method Mapping

The mapping of file patterns to extraction methods (and options) can be specified using a configuration file that is pointed to using the --mapping-file option shown above. OR โ€” plot twist โ€” you can configure the mapping directly in setup.py using a keyword argument to the setup() function:

setup(...

    message_extractors = {
        'foobar': [
            ('**.py',                'python', None),
            ('**/templates/**.html', 'genshi', None),
            ('**/templates/**.txt',  'genshi', {
                'template_class': 'genshi.template:TextTemplate'
            })
        ],
    },

    ...
)

That nested dict is giving main character energy, and honestly? It ate. ๐Ÿ’œ

โœจ Options โœจ

okay bestie, extract_messages is serving options and we're listing ALL of them, no skips ๐Ÿ’…

  • --charset โ€” the charset to use in the output file
  • --keywords (-k) โ€” a space-separated list of keywords to look for, on top of the defaults
  • --no-default-keywords โ€” do not include the default keywords
  • --mapping-file (-F) โ€” path to the mapping configuration file
  • --no-location โ€” do not include location comments with filename and line number
  • --omit-header โ€” do not include the msgid โ€œโ€ entry in the header
  • --output-file (-o) โ€” name of the output file
  • --width (-w) โ€” set the output line width (default 76)
  • --no-wrap โ€” do not break long message lines (longer than the output line width) into several lines
  • --input-dirs โ€” directories that should be scanned for messages
  • --sort-output โ€” generate sorted output (default False)
  • --sort-by-file โ€” sort output by file location (default False)
  • --msgid-bugs-address โ€” set the email address for message bug reports
  • --copyright-holder โ€” set the copyright holder in the output
  • --add-comments (-c) โ€” place a comment block with TAG (or those preceding keyword lines) in the output file. Separate multiple TAGs with commas (,)

now the โœจplot twistโœจ: you can drop these options either on the command-line or in the setup.cfg file. When it's the config file, those same options become entries of the [extract_messages] section, and the names switch from dashes to underscore characters. like so:

[extract_messages]
keywords = _ gettext ngettext
mapping_file = mapping.cfg
width = 80

and that whole thing is equivalent to running this from the command-line:

$ setup.py extract_messages -k _ -k gettext -k ngettext -F mapping.cfg -w 80

Any path names get interpreted relative to the location of the setup.py file. And for boolean options, you use โ€œtrueโ€ or โ€œfalseโ€ values. periodt. ๐ŸŒธ


๐Ÿฆ‹ init_catalog ๐Ÿฆ‹

init_catalog is basically the GNU msginit program's twin fr fr: it creates a brand new translation catalog based on a PO template file (POT). iconic behaviour honestly.

If the command has been correctly installed or registered, a project's setup.py script should let you use the command like this:

$ ./setup.py init_catalog --help
Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message

Options for 'init_catalog' command:
  ...

Running the command will produce a PO file:

$ ./setup.py init_catalog -l fr -i foobar/locales/messages.pot \
                         -o foobar/locales/fr/messages.po
running init_catalog
creating catalog 'foobar/locales/fr/messages.po' based on 'foobar/locales/messages.pot'

๐Ÿ’– Options ๐Ÿ’–

The init_catalog command accepts the following options:

  • --domain โ€” domain of the PO file (defaults to the lower-cased project name)
  • --input-file (-i) โ€” name of the input file
  • --output-dir (-d) โ€” name of the output directory
  • --output-file (-o) โ€” name of the output file
  • --locale โ€” locale for the new localized string

If output-dir is specified, but output-file is not, the default filename of the output file will be:

<output_dir>/<locale>/LC_MESSAGES/<domain>.po

These options can either be specified on the command-line, or in the setup.cfg file. ๐ŸŽ€


๐ŸŒˆ update_catalog ๐ŸŒˆ

update_catalog is basically equivalent to the GNU msgmerge program: it updates an existing translations catalog based on a PO template file (POT). it's giving glow-up for your translations โœจ

If the command has been correctly installed or registered, a project's setup.py script should allow you to use the command:

$ ./setup.py update_catalog --help
Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message

Options for 'update_catalog' command:
  ...

Running the command will update a PO file:

$ ./setup.py update_catalog -l fr -i foobar/locales/messages.pot \
                            -o foobar/locales/fr/messages.po
running update_catalog
updating catalog 'foobar/locales/fr/messages.po' based on 'foobar/locales/messages.pot'

โœจ Options โœจ

So update_catalog takes these cuties, bestie, and every single one of them is a whole mood:

  • --domain โ€” the domain of the PO file (and yeah, it defaults to the lower-cased project name, cute right)
  • --input-file (-i) โ€” the name of the input file
  • --output-dir (-d) โ€” the name of the output directory
  • --output-file (-o) โ€” the name of the output file
  • --locale โ€” the locale for the new localized string
  • --ignore-obsolete โ€” do not include obsolete messages in the output
  • --no-fuzzy-matching (-N) โ€” do not use fuzzy matching
  • --previous โ€” keep previous msgids of translated messages

Now, if you give it output-dir but not output-file, it's gonna pick a default filename for the output file all on its own, and that filename is literally:

<output_dir>/<locale>/LC_MESSAGES/<domain>.po

And if neither the input_file nor the locale option is set? Then this command goes on a lil scavenger hunt: it looks for all catalog files in the base directory that match the given domain, and updates each and every one of them. All of them. Every last one. ๐Ÿ’…

You can pass these options on the command-line, or you can drop them in the setup.cfg file โ€” your call, no pressure.


๐Ÿซถ Support Classes and Functions ๐Ÿซถ

The babel.support module is lowkey a whole gift โ€” it's got a bunch of classes and functions that help you integrate Babel (and internationalization in general) into your app or framework. And here's the tea: the code in this module is not used by Babel itself. Nope. It's just there for you, to handle the common requirements that come up when your app has to deal with i18n. It's giving "we made this for you specifically." ๐ŸŽ€

๐Ÿฆ‹ Lazy Evaluation ๐Ÿฆ‹

One of those requirements is lazy evaluation of translations, and honestly it's so real. Lots of web-based apps define some localizable message at the module level โ€” or, like, at any level where the locale of the remote user isn't known yet. Awkward. For those cases, web frameworks generally hand you a "lazy" variant of the gettext functions, which basically means: it does not translate the message when the gettext function is invoked, but instead translates it when the string is accessed in some manner. Delayed gratification, but make it i18n. โœจ

๐ŸŒˆ Extended Translations Class ๐ŸŒˆ

Many web-based apps are built out of a whole variety of different components (possibly using some kind of plugin system), and some of those components bring their own message catalogs that need to get integrated into the bigger system. It's giving "found family of translation files."

To support this usage pattern, Babel provides a Translations class that is derived from the GNUTranslations class in the gettext module. This class adds a merge() method that takes another Translations instance, and merges the content of the latter into the main catalog:

translations = Translations.load('main')
translations.merge(Translations.load('plugin1'))

Merging catalogs. Iconic behavior, no cap. ๐Ÿ”ฅ


๐Ÿ‘‘ API REFERENCE ๐Ÿ‘‘

The API reference lists the full public API that Babel provides.

๐Ÿ’œ API Reference ๐Ÿ’œ

This part of the documentation contains the full API reference of the public API of Babel. Yes bestie, all of it.

โญ Core Functionality โญ

The core API provides the basic core functionality. Primarily it provides the Locale object and ways to create it. This object encapsulates a locale and exposes all the data it contains. She's the main character, fr.

All the core functionality is also directly importable from the babel module for convenience. Because we love convenience.

๐ŸŒธ Basic Interface ๐ŸŒธ

class babel.core.Locale(language: str, territory: str | None = None, script: str | None = None, variant: str | None = None, modifier: str | None = None)

class babel.core.Locale(language: str, territory: str | None = None,
script: str | None = None, variant: str | None = None, modifier: str |
None = None)

Representation of a specific locale. Okay so watch this:

>>> locale = Locale('en', 'US')
>>> repr(locale)
"Locale('en', territory='US')"
>>> locale.display_name
u'English (United States)'

A Locale object can also be instantiated from a raw locale string:

>>> locale = Locale.parse('en-US', sep='-')
>>> repr(locale)
"Locale('en', territory='US')"

And 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['latn']['decimal']
u'.'

But listen โ€” if a locale is requested for which no locale data is available, an UnknownLocaleError is raised. So be careful out here:

>>> Locale.parse('en_XX')
Traceback (most recent call last):
    ...
UnknownLocaleError: unknown locale 'en_XX'

For more information see RFC 3066. ๐Ÿ“–

property character_order: str

The text direction for the language.

>>> Locale('de', 'DE').character_order
'left-to-right'
>>> Locale('ar', 'SA').character_order
'right-to-left'

property compact_currency_formats: LocaleDataDict

Locale patterns for compact currency number formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
<NumberPattern u'ยค0K'>

property compact_decimal_formats: LocaleDataDict

Locale patterns for compact decimal number formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
<NumberPattern u'0K'>

property currencies: LocaleDataDict

Mapping of currency codes to translated currency names. Heads up though: this only returns the generic form of the currency name, not the count specific one. So if an actual number is requested, use the babel.numbers.get_currency_name() function instead, bestie.

>>> Locale('en').currencies['COP']
u'Colombian Peso'
>>> Locale('de', 'DE').currencies['COP']
u'Kolumbianischer Peso'

property currency_formats: LocaleDataDict

Locale patterns for currency number formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en', 'US').currency_formats['standard']
<NumberPattern u'\xa4#,##0.00'>
>>> Locale('en', 'US').currency_formats['accounting']
<NumberPattern u'\xa4#,##0.00;(\xa4#,##0.00)'>

property currency_symbols: LocaleDataDict

Mapping of currency codes to symbols.

>>> Locale('en', 'US').currency_symbols['USD']
u'$'
>>> Locale('es', 'CO').currency_symbols['USD']
u'US$'

property date_formats: LocaleDataDict

Locale patterns for date formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en', 'US').date_formats['short']
<DateTimePattern u'M/d/yy'>
>>> Locale('fr', 'FR').date_formats['long']
<DateTimePattern u'd MMMM y'>

property datetime_formats: LocaleDataDict

Locale patterns for datetime formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en').datetime_formats['full']
u'{1}, {0}'
>>> Locale('th').datetime_formats['medium']
u'{1} {0}'

property datetime_skeletons: LocaleDataDict

Locale patterns for formatting parts of a datetime.

>>> Locale('en').datetime_skeletons['MEd']
<DateTimePattern u'E, M/d'>
>>> Locale('fr').datetime_skeletons['MEd']
<DateTimePattern u'E dd/MM'>
>>> Locale('fr').datetime_skeletons['H']
<DateTimePattern u"HH 'h'">

property day_period_rules: LocaleDataDict

Day period rules for the locale. Used by get_period_id.

property day_periods: LocaleDataDict

Locale display names for various day periods (not necessarily only AM/PM).

Real talk though: these are not meant to be used without the relevant day_period_rules. They're a package deal, okay? ๐Ÿซถ

property days: LocaleDataDict

Locale display names for weekdays.

>>> Locale('de', 'DE').days['format']['wide'][3]
u'Donnerstag'

property decimal_formats: LocaleDataDict

Locale patterns for decimal number formatting.

NOTE: The format of the value returned may change between Babel versions.
>>> Locale('en', 'US').decimal_formats[None]
<NumberPattern u'#,##0.###'>

classmethod default(category: str | None = None, aliases: Mapping[str, str] = {'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'}) -> Locale

bestie this one hands you the system default locale for whatever category you ask for, it's giving "just tell me what the machine is vibing with" ๐Ÿ’–

>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
...     os.environ[name] = ''
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
>>> Locale.default('LC_MESSAGES')
Locale('fr', territory='FR')

The fallback chain to that variable is always checked in this order, no skips:

  • LANGUAGE
  • LC_ALL
  • LC_CTYPE
  • LANG

Parameters

  • category โ€” one of the LC_XXX environment variable names
  • aliases โ€” a dictionary of aliases for locale identifiers

property default_numbering_system: str

The default numbering system the locale rolls with. ๐Ÿฆ‹

>>> Locale('el', 'GR').default_numbering_system
u'latn'

property display_name: str | None

The localized display name of the locale โ€” like, in its own language, main character energy. ๐Ÿ‘‘

>>> Locale('en').display_name
u'English'
>>> Locale('en', 'US').display_name
u'English (United States)'
>>> Locale('sv').display_name
u'svenska'

Type โ€” unicode

property english_name: str | None

The english display name of the locale, for when you need it in English specifically. ๐ŸŒธ

>>> Locale('de').english_name
u'German'
>>> Locale('de', 'DE').english_name
u'German (Germany)'

Type โ€” unicode

property eras: LocaleDataDict

Locale display names for eras.

NOTE: The format of the value returned may change between Babel versions. ๐Ÿ’€
>>> Locale('en', 'US').eras['wide'][1]
u'Anno Domini'
>>> Locale('en', 'US').eras['abbreviated'][0]
u'BC'

property first_week_day: int

The first day of a week, where 0 is Monday. ๐Ÿ—“๏ธ

>>> Locale('de', 'DE').first_week_day
0
>>> Locale('en', 'US').first_week_day
6

get_display_name(locale: Locale | str | None = None) -> str | None

Returns the display name of the locale using whatever locale you hand it.

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, China)'

Modifiers are currently passed through verbatim:

>>> Locale('it', 'IT', modifier='euro').get_display_name('en')
u'Italian (Italy, euro)'

Parameters

  • locale โ€” the locale to use

get_language_name(locale: Locale | str | None = None) -> str | None

Returns the language of this locale, expressed in the given locale. ๐Ÿซถ

>>> Locale('zh', 'CN', script='Hans').get_language_name('de')
u'Chinesisch'

Added in version 1.0.

Parameters

  • locale โ€” the locale to use

get_script_name(locale: Locale | str | None = None) -> str | None

Returns the script name in the given locale. ๐ŸŽ€

get_territory_name(locale: Locale | str | None = None) -> str | None

Returns the territory name in the given locale. ๐ŸŒˆ

property interval_formats: LocaleDataDict

Locale patterns for interval formatting.

NOTE: The format of the value returned may change between Babel versions. ๐Ÿ’€

Here's how you format date intervals in Finnish when the day is the smallest changing component:

>>> Locale('fi_FI').interval_formats['MEd']['d']
[u'E d. โ€“ ', u'E d.M.']

SEE ALSO: The primary API to use this data is babel.dates.format_interval().

Return type โ€” dict[str, dict[str, list[str]]]

language

the language code

property language_name: str | None

The localized language name of the locale. ๐Ÿ’œ

>>> Locale('en', 'US').language_name
u'English'

property languages: LocaleDataDict

Mapping of language codes to translated language names.

>>> Locale('de', 'DE').languages['ja']
u'Japanisch'

See ISO 639 for more information. ๐Ÿ“š

property list_patterns: LocaleDataDict

Patterns for generating lists.

NOTE: The format of the value returned may change between Babel versions. ๐Ÿ’€
>>> Locale('en').list_patterns['standard']['start']
u'{0}, {1}'
>>> Locale('en').list_patterns['standard']['end']
u'{0}, and {1}'
>>> Locale('en_GB').list_patterns['standard']['end']
u'{0} and {1}'

property measurement_systems: LocaleDataDict

Localized names for various measurement systems. ๐Ÿ“

>>> Locale('fr', 'FR').measurement_systems['US']
u'am\xe9ricain'
>>> Locale('en', 'US').measurement_systems['US']
u'US'

property meta_zones: LocaleDataDict

Locale display names for meta time zones.

Meta time zones are basically groups of different Olson time zones that have the same GMT offset and daylight savings time. ๐ŸŒ

NOTE: The format of the value returned may change between Babel versions. ๐Ÿ’€
>>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
u'Central European Summer Time'

Added in version 0.9.

property min_week_days: int

The minimum number of days in a week so that the week counts as the first week of a year or month. โœจ

>>> Locale('de', 'DE').min_week_days
4

modifier

the modifier

property months: LocaleDataDict

Locale display names for months. ๐Ÿ“

>>> Locale('de', 'DE').months['format']['wide'][10]
u'Oktober'

classmethod negotiate(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = {'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'}) -> Locale | None

Finds the best match between the available and requested locale strings โ€” the matchmaker of locale identifiers, lowkey iconic. ๐Ÿ’…

>>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
Locale('de', territory='DE')
>>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
Locale('de')
>>> Locale.negotiate(['de_DE', 'de'], ['en_US'])

You can specify the character used in the locale identifiers to separate the different components. This separator is applied to both lists. Also, case is ignored in the comparison: ๐Ÿ‘‘

>>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
Locale('de', territory='DE')

Parameters

  • preferred โ€” the list of locale identifiers preferred by the user
  • available โ€” the list of locale identifiers available
  • aliases โ€” a dictionary of aliases for locale identifiers
  • sep โ€” separator for parsing; e.g. Windows tends to use '-' instead of '_'.

property number_symbols: LocaleDataDict

Symbols used in number formatting, by number system. ๐Ÿ”ข

NOTE: The format of the value returned may change between Babel versions. ๐Ÿ’€
>>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
u','
>>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
u'ูซ'
>>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
u'.'

property ordinal_form: PluralRule

Plural rules for the locale. ๐ŸŒธ

>>> Locale('en').ordinal_form(1)
'one'
>>> Locale('en').ordinal_form(2)
'two'
>>> Locale('en').ordinal_form(3)
'few'
>>> Locale('fr').ordinal_form(2)
'other'
>>> Locale('ru').ordinal_form(100)
'other'

property other_numbering_systems: LocaleDataDict</p>

Mapping of other numbering systems available for the locale. See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems

>>> Locale('el', 'GR').other_numbering_systems['traditional']
u'grek'
NOTE: the format of the value returned may change between Babel versions. ๐Ÿ‘€ so like, don't hardcode your whole personality around it bestie.

classmethod parse(identifier: Locale | str | None, sep: str = '_', resolve_likely_subtags: bool = True) -> Locale</p>

Create a Locale instance for the given locale identifier. Slay, this is the glow-up machine โœจ

>>> l = Locale.parse('de-DE', sep='-')
>>> l.display_name
u'Deutsch (Deutschland)'

If the identifier parameter is not a string, but actually a Locale object, that object is returned:

>>> Locale.parse(l)
Locale('de', territory='DE')

If the identifier parameter is neither of these, such as None or an empty string, e.g. because a default locale identifier could not be determined, a TypeError is raised:

>>> Locale.parse(None)
Traceback (most recent call last):
    ...
TypeError: ...

This also can perform resolving of likely subtags which it does by default. This is for instance useful to figure out the most likely locale for a territory you can use 'und' as the language tag:

>>> Locale.parse('und_AT')
Locale('de', territory='AT')

Modifiers are optional, and always at the end, separated by "@":

>>> Locale.parse('de_AT@euro')
Locale('de', territory='AT', modifier='euro')

Parameters</p>

  • identifier โ€” the locale identifier string
  • sep โ€” optional component separator
  • resolve_likely_subtags โ€” if this is specified then a locale will have its likely subtag resolved if the locale otherwise does not exist. For instance zh_TW by itself is not a locale that exists but Babel can automatically expand it to the full form of zh_hant_TW. Note that this expansion is only taking place if no locale exists otherwise. For instance there is a locale en that can exist by itself.

Raises</p>

  • ValueError โ€” if the string does not appear to be a valid locale identifier
  • UnknownLocaleError โ€” if no locale data is available for the requested locale
  • TypeError โ€” if the identifier is not a string or a Locale
  • ValueError โ€” if the identifier is not a valid string

property percent_formats: LocaleDataDict</p>

Locale patterns for percent number formatting.

NOTE: the format of the value returned may change between Babel versions. ๐Ÿ’€
>>> Locale('en', 'US').percent_formats[None]
<NumberPattern u'#,##0%'>

property periods: LocaleDataDict</p>

Locale display names for day periods (AM/PM). it's giving clock energy ๐Ÿ•

>>> Locale('en', 'US').periods['am']
u'AM'

property plural_form: PluralRule</p>

Plural rules for the locale. fr fr the grammar police but make it code ๐ŸŒธ

>>> Locale('en').plural_form(1)
'one'
>>> Locale('en').plural_form(0)
'other'
>>> Locale('fr').plural_form(0)
'one'
>>> Locale('ru').plural_form(100)
'many'

property quarters: LocaleDataDict</p>

Locale display names for quarters.

>>> Locale('de', 'DE').quarters['format']['wide'][1]
u'1. Quartal'

property scientific_formats: LocaleDataDict</p>

Locale patterns for scientific number formatting. nerdy but iconic ๐Ÿงช

NOTE: the format of the value returned may change between Babel versions.
>>> Locale('en', 'US').scientific_formats[None]
<NumberPattern u'#E0'>

script</p>

the script code


property script_name: str | None</p>

The localized script name of the locale if available.

>>> Locale('sr', 'ME', script='Latn').script_name
u'latinica'

property scripts: LocaleDataDict</p>

Mapping of script codes to translated script names.

>>> Locale('en', 'US').scripts['Hira']
u'Hiragana'

See ISO 15924 for more information. ๐Ÿฆ‹


property territories: LocaleDataDict</p>

Mapping of script codes to translated script names.

>>> Locale('es', 'CO').territories['DE']
u'Alemania'

See ISO 3166 for more information. ๐ŸŒˆ


territory</p>

the territory (country or region) code


property territory_name: str | None</p>

The localized territory name of the locale if available.

>>> Locale('de', 'DE').territory_name
u'Deutschland'

property text_direction: str</p>

The text direction for the language in CSS short-hand form. left to right or right to left, no in-between ๐Ÿ’…

>>> Locale('de', 'DE').text_direction
'ltr'
>>> Locale('ar', 'SA').text_direction
'rtl'

property time_formats: LocaleDataDict</p>

Locale patterns for time formatting.

NOTE: the format of the value returned may change between Babel versions. bestie this note is everywhere, get used to it ๐Ÿ˜ญ
>>> Locale('en', 'US').time_formats['short']
<DateTimePattern u'h:mm a'>
>>> Locale('fr', 'FR').time_formats['long']
<DateTimePattern u'HH:mm:ss z'>

property time_zones: LocaleDataDict</p>

Locale display names for time zones.

NOTE: the format of the value returned may change between Babel versions.
>>> 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'

property unit_display_names: LocaleDataDict</p>

Display names for units of measurement. km, kg, and vibes ๐Ÿ“

SEE ALSO: you may want to use babel.units.get_unit_name() instead. ๐Ÿ‘‘

NOTE: the format of the value returned may change between Babel versions.

variant</p>

the variant code


property variants: LocaleDataDict</p>

Mapping of script codes to translated script names.

>>> Locale('de', 'DE').variants['1901']
u'Alte deutsche Rechtschreibung'

property weekend_end: int</p>

The day the weekend ends, with 0 being Monday. counting from Monday bestie, stay woke ๐Ÿ“…

>>> Locale('de', 'DE').weekend_end
6

property weekend_start: int</p>

The day the weekend starts, with 0 being Monday.

>>> Locale('de', 'DE').weekend_start
5

property zone_formats: LocaleDataDict</p>

Patterns related to the formatting of time zones.

NOTE: the format of the value returned may change between Babel versions.
>>> Locale('en', 'US').zone_formats['fallback']
u'%(1)s (%(0)s)'
>>> Locale('pt', 'BR').zone_formats['region']
u'Hor\xe1rio %s'

Added in version 0.9. โœจ


babel.core.default_locale(category: str | tuple[str, ...] | list[str] | None = None, aliases: Mapping[str, str] = {'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'}) -> str | None

Returns the system default locale for a given category, based on environment variables. your system's whole language mood, detected automatically ๐Ÿซถ

>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
...     os.environ[name] = ''
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
>>> default_locale('LC_MESSAGES')
'fr_FR'

The "C" or "POSIX" pseudo-locales are treated as aliases for the "en_US_POSIX" locale:

>>> os.environ['LC_MESSAGES'] = 'POSIX'
>>> default_locale('LC_MESSAGES')
'en_US_POSIX'

The following fallbacks to the variable are always considered: ๐Ÿ”

  • LANGUAGE
  • LC_ALL
  • LC_CTYPE
  • LANG

Parameters</p>

  • category โ€” one or more of the LC_XXX environment variable names
  • aliases โ€” a dictionary of aliases for locale identifiers

babel.core.negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = {'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'}) -> str | None

Find the best match between available and requested locale strings. it's literally a matchmaker for locales ๐Ÿ’– the way that it finds your perfect fit, no cap.

>>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) 'de_DE' >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de']) 'de'

So like, the algorithm totally ignores case, but the result it hands back uses the case of the preferred locale identifier:

>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
'de_DE'
>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
'de_DE'

Okay so here's the tea โ˜•: by default, some web browsers unfortunately do not include the territory in the locale identifier for many locales, and some don't even let the user easily add the territory. So even if you'd rather use fully qualified locale identifiers in your web-app, they wouldn't normally match the language-only locale that those browsers send. To work around that whole situation, this function uses a default mapping of commonly used language-only locale identifiers over to identifiers that actually include the territory:

>>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
'ja_JP'

And some browsers will straight-up use an incorrect or outdated language code โ€” like "no" for Norwegian, when the correct locale identifier would actually be "nb_NO" (Bokmรฅl) or "nn_NO" (Nynorsk). The aliases are designed to handle those cases too, bestie:

>>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
'nb_NO'

You can override this default mapping by passing a different aliases dictionary into this function, or you can bypass the behavior altogether by setting the aliases parameter to None. ๐Ÿซถ

Parameters

  • preferred โ€” the list of locale strings preferred by the user
  • available โ€” the list of locale strings available
  • sep โ€” character that separates the different parts of the locale strings
  • aliases โ€” a dictionary of aliases for locale identifiers

๐Ÿ˜ญ Exceptions

exception babel.core.UnknownLocaleError(identifier: str)

Exception thrown when a locale is requested for which no locale data is available. It's giving "you asked for a locale and I simply do not have it, so sorry." ๐Ÿ’€

  • identifier โ€” The identifier of the locale that could not be found.

๐Ÿ› ๏ธ Utility Functions

babel.core.get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]

Return the dictionary for the given key in the global data.

The global data lives in the babel/global.dat file and holds information that's independent of individual locales. Iconic.

>>> get_global('zone_aliases')['UTC']
u'Etc/UTC'
>>> get_global('zone_territories')['Europe/Berlin']
u'DE'

The keys available are:

  • all_currencies
  • currency_fractions
  • language_aliases
  • likely_subtags
  • parent_exceptions
  • script_aliases
  • territory_aliases
  • territory_currencies
  • territory_languages
  • territory_zones
  • variant_aliases
  • windows_zone_mapping
  • zone_aliases
  • zone_territories
NOTE: The internal structure of the data may change between versions. So don't get too attached, bestie. ๐Ÿ™ˆ

Added in version 0.9.

Parameters

  • key โ€” the data key

babel.core.parse_locale(identifier: str, sep: str = '_') -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]

Parse a locale identifier into a tuple of the form (language, territory, script, variant, modifier).

>>> parse_locale('zh_CN')
('zh', 'CN', None, None)
>>> parse_locale('zh_Hans_CN')
('zh', 'CN', 'Hans', None)
>>> parse_locale('ca_es_valencia')
('ca', 'ES', None, 'VALENCIA')
>>> parse_locale('en_150')
('en', '150', None, None)
>>> parse_locale('en_us_posix')
('en', 'US', None, 'POSIX')
>>> parse_locale('it_IT@euro')
('it', 'IT', None, None, 'euro')
>>> parse_locale('it_IT@custom')
('it', 'IT', None, None, 'custom')
>>> parse_locale('it_IT@')
('it', 'IT', None, None)

The default component separator is "_", but you can specify a different separator with the sep parameter. Slay, flexibility. โœจ

The optional modifier is always separated with "@" and sits right at the end:

>>> parse_locale('zh-CN', sep='-')
('zh', 'CN', None, None)
>>> parse_locale('zh-CN@custom', sep='-')
('zh', 'CN', None, None, 'custom')

If the identifier cannot be parsed into a locale, a ValueError exception gets raised:

>>> parse_locale('not_a_LOCALE_String')
Traceback (most recent call last):
  ...
ValueError: 'not_a_LOCALE_String' is not a valid locale identifier

Encoding info gets removed from the identifier, while modifiers are kept:

>>> parse_locale('en_US.UTF-8')
('en', 'US', None, None)
>>> parse_locale('de_DE.iso885915@euro')
('de', 'DE', None, None, 'euro')

See RFC 4646 for more information. iykyk ๐Ÿ“š

Parameters

  • identifier โ€” the locale identifier string
  • sep โ€” character that separates the different components of the locale identifier

Raises ValueError โ€” if the string does not appear to be a valid locale identifier

babel.core.get_locale_identifier(tup: tuple[str] | tuple[str, str | None] | tuple[str, str | None, str | None] | tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None], sep: str = '_') -> str

The reverse of parse_locale(). It creates a locale identifier out of a (language, territory, script, variant, modifier) tuple. Items can be set to None, and trailing Nones can also be left out of the tuple entirely.

>>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
'de_DE_1999@custom'
>>> get_locale_identifier(('fi', None, None, None, 'custom'))
'fi@custom'

Added in version 1.0.

Parameters

  • tup โ€” the tuple as returned by parse_locale().
  • sep โ€” the separator for the identifier.

๐Ÿ“… Date and Time

The date and time functionality that Babel provides lets you format standard Python datetime, date and time objects and also work with timezones. It's giving very useful energy. ๐Ÿฆ‹

โœจ Date and Time Formatting

babel.dates.format_datetime(datetime=None, format='medium', tzinfo=None, locale=default_locale('LC_TIME'))

Return a date formatted according to the given pattern.

>>> from datetime import datetime
>>> dt = datetime(2007, 4, 1, 15, 30)
>>> format_datetime(dt, locale='en_US')
u'Apr 1, 2007, 3:30:00\u202fPM'

For any pattern requiring the display of the timezone:

>>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),
...                 locale='fr_FR')
'dimanche 1 avril 2007, 17:30:00 heure dโ€™รฉtรฉ dโ€™Europe centrale'
>>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
...                 tzinfo=get_timezone('US/Eastern'), locale='en')
u'2007.04.01 AD at 11:30:00 EDT'

Parameters

  • datetime โ€” the datetime object; if None, the current date and time is used
  • format โ€” one of "full", "long", "medium", or "short", or a custom date/time pattern
  • tzinfo โ€” the timezone to apply to the time for display
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.

babel.dates.format_date(date=None, format='medium', locale=default_locale('LC_TIME'))

Return a date formatted according to the given pattern.

>>> from datetime import date
>>> d = date(2007, 4, 1)
>>> format_date(d, locale='en_US')
u'Apr 1, 2007'
>>> format_date(d, format='full', locale='de_DE')
u'Sonntag, 1. April 2007'

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"

Parameters

  • date โ€” the date or datetime object; if None, the current date is used
  • format โ€” one of "full", "long", "medium", or "short", or a custom date/time pattern
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.

babel.dates.format_time(time=None, format='medium', tzinfo=None, locale=default_locale('LC_TIME'))

Return a time formatted according to the given pattern.

>>> from datetime import datetime, time
>>> t = time(15, 30)
>>> format_time(t, locale='en_US')
u'3:30:00\u202fPM'
>>> format_time(t, format='short', locale='de_DE')
u'15:30'

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"

For any pattern requiring the display of the time-zone, a timezone has to be specified explicitly:

>>> t = datetime(2007, 4, 1, 15, 30)
>>> tzinfo = get_timezone('Europe/Paris')
>>> t = _localize(tzinfo, t)
>>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
'15:30:00 heure dโ€™รฉtรฉ dโ€™Europe centrale'
>>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),
...             locale='en')
u"09 o'clock AM, Eastern Daylight Time"

As that example shows, when this function gets passed a datetime.datetime value, the actual time in the formatted string is adjusted to the timezone specified by the tzinfo parameter. And if the datetime is "naive" (i.e. it has no associated timezone information), it is assumed to be in UTC. No cap. ๐Ÿซถ

These timezone calculations are not performed if the value is of type datetime.time, bestie, because without date info there is literally no way to know what a given time translates to in another timezone โ€” you'd need to know if daylight savings time is in effect or not. ๐Ÿ’€ So time values are just left as-is, and tzinfo is only used to display the timezone name if needed:

>>> t = time(15, 30)
>>> format_time(t, format='full', tzinfo=get_timezone('Europe/Paris'),
...             locale='fr_FR')
u'15:30:00 heure normale d\u2019Europe centrale'
>>> format_time(t, format='full', tzinfo=get_timezone('US/Eastern'),
...             locale='en_US')
u'3:30:00\u202fPM Eastern Standard Time'

โœจ Parameters โœจ

  • time โ€” the time or datetime object; if None, the current time in UTC is used โฐ
  • format โ€” one of "full", "long", "medium", or "short", or a custom date/time pattern
  • tzinfo โ€” the time-zone to apply to the time for display ๐ŸŒ
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.

babel.dates.format_timedelta(delta, granularity='second', threshold=.85, add_direction=False, format='long', locale=default_locale('LC_TIME'))

Return a time delta according to the rules of the given locale. ๐Ÿ•ฐ๏ธ

>>> from datetime import timedelta
>>> format_timedelta(timedelta(weeks=12), locale='en_US')
u'3 months'
>>> format_timedelta(timedelta(seconds=1), locale='es')
u'1 segundo'

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'

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 hours'

In addition directional information can be provided that informs the user if the date is in the past or in the future:

>>> format_timedelta(timedelta(hours=1), add_direction=True, locale='en')
u'in 1 hour'
>>> format_timedelta(timedelta(hours=-1), add_direction=True, locale='en')
u'1 hour ago'

The format parameter controls how compact or wide the presentation is:

>>> format_timedelta(timedelta(hours=3), format='short', locale='en')
u'3 hr'
>>> format_timedelta(timedelta(hours=3), format='narrow', locale='en')
u'3h'

โœจ Parameters โœจ

  • delta โ€” a timedelta object representing the time difference to format, or the delta in seconds as an int value
  • granularity โ€” determines the smallest unit that should be displayed, the value can be one of "year", "month", "week", "day", "hour", "minute" or "second" ๐Ÿฆ‹
  • threshold โ€” factor that determines at which point the presentation switches to the next higher unit
  • add_direction โ€” if this flag is set to True the return value will include directional information. For instance a positive timedelta will include the information about it being in the future, a negative will be information about the value being in the past. โชโฉ
  • format โ€” the format, can be "narrow", "short" or "long". ( "medium" is deprecated, currently converted to "long" to maintain compatibility)
Deprecation tea โ˜•: "medium" is deprecated โ€” right now it just gets converted to "long" to keep everything compatible, but don't build your whole personality around it.
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.

babel.dates.format_skeleton(skeleton, datetime=None, tzinfo=None, fuzzy=True, locale=default_locale('LC_TIME'))

Return a time and/or date formatted according to the given pattern. โœจ

The skeletons are defined in the CLDR data and provide more flexibility than the simple short/long/medium formats, but are a bit harder to use. The are defined using the date/time symbols without order or punctuation and map to a suitable format for the given locale.

>>> from datetime import datetime
>>> t = datetime(2007, 4, 1, 15, 30)
>>> format_skeleton('MMMEd', t, locale='fr')
u'dim. 1 avr.'
>>> format_skeleton('MMMEd', t, locale='en')
u'Sun, Apr 1'
>>> format_skeleton('yMMd', t, locale='fi')  # yMMd is not in the Finnish locale; yMd gets used
u'1.4.2007'
>>> format_skeleton('yMMd', t, fuzzy=False, locale='fi')  # yMMd is not in the Finnish locale, an error is thrown
Traceback (most recent call last):
    ...
KeyError: yMMd
>>> format_skeleton('GH', t, fuzzy=True, locale='fi_FI')  # GH is not in the Finnish locale and there is no close match, an error is thrown
Traceback (most recent call last):
    ...
KeyError: None

After the skeleton is resolved to a pattern format_datetime is called so all timezone processing etc is the same as for that.

โœจ Parameters โœจ

  • skeleton โ€” A date time skeleton as defined in the cldr data.
  • datetime โ€” the time or datetime object; if None, the current time in UTC is used
  • tzinfo โ€” the time-zone to apply to the time for display ๐ŸŒ
  • fuzzy โ€” If the skeleton is not found, allow choosing a skeleton that's close enough to it. If there is no close match, a KeyError is thrown. ๐Ÿ’€
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.

babel.dates.format_interval(start, end, skeleton=None, tzinfo=None, fuzzy=True, locale=default_locale('LC_TIME'))

Format an interval between two instants according to the locale's rules. ๐ŸŽ€

>>> from datetime import date, time
>>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "yMd", locale="fi")
u'15.โ€“17.1.2016'

>>> format_interval(time(12, 12), time(16, 16), "Hm", locale="en_GB")
'12:12โ€“16:16'

>>> format_interval(time(5, 12), time(16, 16), "hm", locale="en_US")
'5:12 AM โ€“ 4:16 PM'

>>> format_interval(time(16, 18), time(16, 24), "Hm", locale="it")
'16:18โ€“16:24'

If the start instant equals the end instant, the interval is formatted like the instant. ๐Ÿซถ

>>> format_interval(time(16, 18), time(16, 18), "Hm", locale="it")
'16:18'

Unknown skeletons fall back to "default" formatting.

>>> format_interval(date(2015, 1, 1), date(2017, 1, 1), "wzq", locale="ja")
'2015/01/01๏ฝž2017/01/01'

>>> format_interval(time(16, 18), time(16, 24), "xxx", locale="ja")
'16:18:00๏ฝž16:24:00'

>>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "xxx", locale="de")
'15.01.2016 โ€“ 17.01.2016'

โœจ Parameters โœจ

  • start โ€” First instant (datetime/date/time)
  • end โ€” Second instant (datetime/date/time)
  • skeleton โ€” The "skeleton format" to use for formatting.
  • tzinfo โ€” tzinfo to use (if none is already attached)
  • fuzzy โ€” If the skeleton is not found, allow choosing a skeleton that's close enough to it.
  • locale โ€” A locale object or identifier. Defaults to the system time locale.

โœจ Returns โœจ

Formatted interval ๐Ÿ’…


๐ŸŒ Timezone Functionality ๐ŸŒ

babel.dates.get_timezone(zone: str | tzinfo | None = None) -> tzinfo

Looks up a timezone by name and returns it. The timezone object returned comes from pytz or zoneinfo, whichever is available. It corresponds to the tzinfo interface and can be used with all of the functions of Babel that operate with dates.

If a timezone is not known a LookupError is raised. If zone is None a local zone object is returned. ๐ŸŒˆ

โœจ Parameters โœจ

zone โ€” the name of the timezone to look up. If a timezone object itself is passed in, it's returned unchanged.


babel.dates.get_timezone_gmt(datetime: _Instant = None, width: Literal['long', 'short', 'iso8601', 'iso8601_short'] = 'long', locale: Locale | str | None = None, return_z: bool = False) -> str

Return the timezone associated with the given datetime object formatted as string indicating the offset from GMT. โฐ

>>> from datetime import datetime
>>> dt = datetime(2007, 4, 1, 15, 30)
>>> get_timezone_gmt(dt, locale='en')
u'GMT+00:00'
>>> get_timezone_gmt(dt, locale='en', return_z=True)
'Z'
>>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
u'+00'
>>> tz = get_timezone('America/Los_Angeles')
>>> dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
>>> get_timezone_gmt(dt, locale='en')
u'GMT-07:00'
>>> get_timezone_gmt(dt, 'short', locale='en')
u'-0700'
>>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
u'-07'

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-07:00'

Added in version 0.9. โœจ

โœจ Parameters โœจ

  • datetime โ€” the datetime object; if None, the current date and time in UTC is used
  • width โ€” either "long" or "short" or "iso8601" or "iso8601_short"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.
  • return_z โ€” True or False; Function returns indicator "Z" when local time offset is 0 ๐Ÿ’…

babel.dates.get_timezone_location(dt_or_tzinfo: _DtOrTzinfo = None, locale: Locale | str | None = None, return_city: bool = False) -> str

Return a representation of the given timezone using "location format". ๐ŸŒ

The result depends on both the local display name of the country and the city that's tied to the time zone, bestie:

>>> tz = get_timezone('America/St_Johns')
>>> print(get_timezone_location(tz, locale='de_DE'))
Kanada (St. Johnโ€™s) (Ortszeit)
>>> print(get_timezone_location(tz, locale='en'))
Canada (St. Johnโ€™s) Time
>>> print(get_timezone_location(tz, locale='en', return_city=True))
St. Johnโ€™s
>>> tz = get_timezone('America/Mexico_City')
>>> get_timezone_location(tz, locale='de_DE')
u'Mexiko (Mexiko-Stadt) (Ortszeit)'

Okay so if the timezone is associated with a country that only uses one single timezone? You just get the localized country name back, no extra garnish:

>>> tz = get_timezone('Europe/Berlin')
>>> get_timezone_name(tz, locale='de_DE')
u'Mitteleurop\xe4ische Zeit'

Added in version 0.9.

Parameters

  • dt_or_tzinfo โ€” the datetime or tzinfo object that decides which timezone we're talking about; if it's None, the current date and time in UTC is assumed
  • locale โ€” the Locale object, or just a locale string. Defaults to the system time locale.
  • return_city โ€” True or False, and if it's True then it returns the exemplar city (location) for the time zone

Returns the localized timezone name using location format


okay next one, she's a big one ๐Ÿ’…

babel.dates.get_timezone_name(dt_or_tzinfo: _DtOrTzinfo = None, width: Literal['long', 'short'] = 'long', uncommon: bool = False, locale: Locale | str | None = None, zone_variant: Literal['generic', 'daylight', 'standard'] | None = None, return_zone: bool = False) -> str

Returns the localized display name for whatever timezone you hand it. You can specify the timezone using a datetime or a tzinfo object, either one works ๐Ÿ’–

>>> from datetime import time
>>> dt = time(15, 30, tzinfo=get_timezone('America/Los_Angeles'))
>>> get_timezone_name(dt, locale='en_US')
u'Pacific Standard Time'
>>> get_timezone_name(dt, locale='en_US', return_zone=True)
'America/Los_Angeles'
>>> get_timezone_name(dt, width='short', locale='en_US')
u'PST'

Now here's the โœจteaโœจ โ€” if this function only gets a tzinfo object and no concrete datetime, the display name it returns is independent of daylight savings time. So it's perfect for stuff like picking timezones, or setting the time of events that recur across DST changes:

>>> tz = get_timezone('America/Los_Angeles')
>>> get_timezone_name(tz, locale='en_US')
u'Pacific Time'
>>> get_timezone_name(tz, 'short', locale='en_US')
u'PT'

And if there's no localized display name available for the timezone, and the timezone is associated with a country that uses only a single timezone, you get the name of that country instead, formatted according to the locale:

>>> tz = get_timezone('Europe/Berlin')
>>> get_timezone_name(tz, locale='de_DE')
u'Mitteleurop\xe4ische Zeit'
>>> get_timezone_name(tz, locale='pt_BR')
u'Hor\xe1rio da Europa Central'

On the flip side, if the country uses multiple timezones, the city gets included in the representation too:

>>> tz = get_timezone('America/St_Johns')
>>> get_timezone_name(tz, locale='de_DE')
u'Neufundland-Zeit'
Heads up bestie โ€” short format is currently not supported for all timezones and all locales. Partly because not every timezone has a short code in every locale. In that case it currently falls back to the long format. ๐Ÿซถ

For more info, go peek at LDML Appendix J: Time Zone Display Names.

Added in version 0.9.

Changed in version 1.0: Added zone_variant support.

Parameters

  • dt_or_tzinfo โ€” the datetime or tzinfo object that determines the timezone; if a tzinfo object is used, the resulting display name will be generic, i.e. independent of daylight savings time; if None, the current date in UTC is assumed
  • width โ€” either "long" or "short"
  • uncommon โ€” deprecated and ignored (rip, she's retired ๐Ÿ’€)
  • zone_variant โ€” defines the zone variation to return. By default the variation is defined from the datetime object passed in. If no datetime object is passed in, the 'generic' variation is assumed. The following values are valid: 'generic', 'daylight' and 'standard'.
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.
  • return_zone โ€” True or False. If true then the function returns the long time zone ID

babel.dates.UTC A timezone object for UTC. ๐ŸŒ

babel.dates.LOCALTZ A timezone object for the computer's local timezone. ๐Ÿ 

๐Ÿฆ‹ Data Access ๐Ÿฆ‹

babel.dates.get_period_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide', context: _Context = 'stand-alone', locale: Locale | str | None = None) -> LocaleDataDict

Returns the names for day periods (y'know, the AM/PM situation) that the locale uses.

>>> get_period_names(locale='en_US')['am']
u'AM'

Parameters

  • width โ€” the width to use, one of "abbreviated", "narrow", or "wide"
  • context โ€” the context, either "format" or "stand-alone"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_day_names(width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide', context: _Context = 'format', locale: Locale | str | None = None) -> LocaleDataDict

Returns the day names the locale uses for the format you ask for. ๐Ÿ—“๏ธ

>>> get_day_names('wide', locale='en_US')[1]
u'Tuesday'
>>> get_day_names('short', locale='en_US')[1]
u'Tu'
>>> get_day_names('abbreviated', locale='es')[1]
u'mar'
>>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
u'D'

Parameters

  • width โ€” the width to use, one of "wide", "abbreviated", "short" or "narrow"
  • context โ€” the context, either "format" or "stand-alone"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_month_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide', context: _Context = 'format', locale: Locale | str | None = None) -> LocaleDataDict

Returns the month names the locale uses 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'

Parameters

  • width โ€” the width to use, one of "wide", "abbreviated", or "narrow"
  • context โ€” the context, either "format" or "stand-alone"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_quarter_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide', context: _Context = 'format', locale: Locale | str | None = None) -> LocaleDataDict

Returns the quarter names the locale uses 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('narrow', locale='de_DE')[1]
u'1'

Parameters

  • width โ€” the width to use, one of "wide", "abbreviated", or "narrow"
  • context โ€” the context, either "format" or "stand-alone"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_era_names(width: Literal['abbreviated', 'narrow', 'wide'] = 'wide', locale: Locale | str | None = None) -> LocaleDataDict

Returns the era names the locale uses 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.'

Parameters

  • width โ€” the width to use, either "wide", "abbreviated", or "narrow"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_date_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = None) -> DateTimePattern

Returns the date formatting patterns the locale uses for the specified format. ๐Ÿ“†

>>> get_date_format(locale='en_US')
<DateTimePattern u'MMM d, y'>
>>> get_date_format('full', locale='de_DE')
<DateTimePattern u'EEEE, d. MMMM y'>

Parameters

  • format โ€” the format to use, one of "full", "long", "medium", or "short"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_datetime_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = None) -> DateTimePattern

Returns the datetime formatting patterns the locale uses for the specified format. โœจ

>>> get_datetime_format(locale='en_US')
u'{1}, {0}'

Parameters

  • format โ€” the format to use, one of "full", "long", "medium", or "short"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale.

babel.dates.get_time_format(format: _PredefinedTimeFormat = 'medium', locale: Locale | str | None = None) -> DateTimePattern

Returns the time formatting patterns the locale uses for the specified format. โฐ

>>> get_time_format(locale='en_US')
<DateTimePattern u'h:mm:ss a'>
>>> get_time_format('full', locale='de_DE')
<DateTimePattern u'HH:mm:ss zzzz'>

Parameters

  • format โ€” the format to use, one of "full", "long", "medium", or "short"
  • locale โ€” the Locale object, or a locale string. Defaults to the system time locale. ๐Ÿ•ฐ๏ธ

Basic Parsing ๐Ÿ”โœจ

babel.dates.parse_date(string: str, locale: Locale | str | None = None, format: _PredefinedTimeFormat | str = 'medium') -> datetime.date

Okay so this one is literally just "give me a string, I'll give you a datetime.date" energy. Parsing era fr fr. ๐Ÿ“…

If you hand it an explicit format, bestie, that format is exactly what gets used. No guessing, no vibes, just the format you said:

>>> parse_date('01.04.2004', format='dd.MM.yyyy')
datetime.date(2004, 4, 1)

But if you give it no format โ€” or if the format you give is one of "full", "long", "medium", or "short" โ€” then the function does a little two-step ๐Ÿ’ƒ: first it tries to read the string as an ISO-8601 date, and then it peeks at the date format for the locale as a hint to figure out the order the date fields are chilling in.

>>> parse_date('4/1/04', locale='en_US')
datetime.date(2004, 4, 1)
>>> parse_date('01.04.2004', locale='de_DE')
datetime.date(2004, 4, 1)
>>> parse_date('2004-04-01', locale='en_US')
datetime.date(2004, 4, 1)
>>> parse_date('2004-04-01', locale='de_DE')
datetime.date(2004, 4, 1)
>>> parse_date('01.04.04', locale='de_DE', format='short')
datetime.date(2004, 4, 1)

Parameters ๐ŸŽ€

  • string โ€” the string containing the date
  • locale โ€” a Locale object or a locale identifier
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale. (yes it's listed twice in the source, no we're not editing the source, that's just how it is ๐Ÿ’€)
  • format โ€” the format to use, either an explicit date format, or one of "full", "long", "medium", or "short" (peep get_time_format for the deets)

babel.dates.parse_time(string: str, locale: Locale | str | None = None, format: _PredefinedTimeFormat | str = 'medium') -> datetime.time

Parse a time out of a string, bestie. โฐ

This one uses the time format for the locale as a little hint to work out what order the time fields are sitting in inside the string.

But if you pass an explicit format? It uses that to parse the time instead. Periodt.

>>> parse_time('15:30:00', locale='en_US')
datetime.time(15, 30)
>>> parse_time('15:30:00', format='H:mm:ss')
datetime.time(15, 30)

Parameters ๐ŸŽ€

  • string โ€” the string containing the time
  • locale โ€” a Locale object or a locale identifier. Defaults to the system time locale.
  • format โ€” the format to use, either an explicit time format, or one of "full", "long", "medium", or "short" (again, see get_time_format)

Returns โ€” the parsed time ๐Ÿซถ

Return type โ€” time


babel.dates.parse_pattern(pattern: str | DateTimePattern) -> DateTimePattern

Parse date, time, and datetime format patterns. This is the pattern decoder ring, no cap. ๐Ÿ”ฎ

>>> parse_pattern("MMMMd").format
u'%(MMMM)s%(d)s'
>>> parse_pattern("MMM d, yyyy").format
u'%(MMM)s %(d)s, %(yyyy)s'

A pattern can contain literal strings wrapped in single quotes, like so:

>>> parse_pattern("H:mm' Uhr 'z").format
u'%(H)s:%(mm)s Uhr %(z)s'

And if you actually want a real single quote in there? You use two adjacent single quote characters. Two quotes, one quote, that's the trade. ๐Ÿ’…

>>> parse_pattern("hh' o''clock'").format
u"%(hh)s o'clock"

Parameters โ€” pattern โ€” the formatting pattern to parse


Languages ๐ŸŒ๐Ÿ’ฌ

The languages module gives you functionality to access data about languages that is not bound to a given locale. Free-range language facts, basically. ๐Ÿฆ‹

Official Languages ๐Ÿ‘‘

babel.languages.get_official_languages(territory: str, regional: bool = False, de_facto: bool = False) -> tuple[str, ...]

Get the official language(s) for the given territory. ๐Ÿ‘‘

The language codes, if any are known, come back in order of descending popularity โ€” most popular first, so the main character is up front. โœจ

If the regional flag is set, then languages which are regionally official are also returned.

If the de_facto flag is set, then languages which are โ€œde factoโ€ official are also returned.

WARNING: note that the data is as up to date as the current version of the CLDR used by Babel. If you need scientifically accurate information, use another source! Not me being dramatic, that's literally what it says. ๐Ÿ˜ญ

Parameters ๐ŸŽ€

  • territory (str) โ€“ Territory code
  • regional (bool) โ€“ Whether to return regionally official languages too
  • de_facto (bool) โ€“ Whether to return de-facto official languages too

Returns โ€” Tuple of language codes

Return type โ€” tuple[str]


babel.languages.get_territory_language_info(territory: str) -> dict[str, dict[str, float | str | None]]

Get a dictionary of language information for a territory. ๐Ÿ—บ๏ธ

The dictionary is keyed by language code; the values are dicts with more information inside. Nested dicts, we love a little structure. ๐Ÿ’–

The following keys are currently known for the values:

  • population_percent: The percentage of the territory's population speaking the language.
  • official_status: An optional string describing the officiality status of the language. Known values are "official", "official_regional" and "de_facto_official".
WARNING: note that the data is as up to date as the current version of the CLDR used by Babel. If you need scientifically accurate information, use another source!
NOTE: note that the format of the dict returned may change between Babel versions. So don't get too attached to the shape of it, bestie. ๐Ÿฅฒ

See โ€ https://www.unicode.org/cldr/charts/latest/supplemental/territory_language_information.html

Parameters โ€” territory (str) โ€“ Territory code

Returns โ€” Language information dictionary

Return type โ€” dict[str, dict]


List Formatting ๐Ÿ“โœจ

This module lets you format lists of items in a locale-dependent manner. Commas, "and", "or" โ€” every locale does it different and this module knows all the lore. ๐ŸŽ€

babel.lists.format_list(lst: Sequence[str], style: Literal['standard', 'standard-short', 'or', 'or-short', 'unit', 'unit-short', 'unit-narrow'] = 'standard', locale: Locale | str | None = None) -> str

Format the items in lst as a list. That's it, that's the job. ๐Ÿ’…

>>> format_list(['apples', 'oranges', 'pears'], locale='en')
u'apples, oranges, and pears'
>>> format_list(['apples', 'oranges', 'pears'], locale='zh')
u'applesใ€orangesๅ’Œpears'
>>> format_list(['omena', 'peruna', 'aplari'], style='or', locale='fi')
u'omena, peruna tai aplari'

Not all styles are necessarily available in all locales, okay? So the function will attempt to fall back to replacement styles according to the rules set forth in the CLDR root XML file, and it raises a ValueError if no suitable replacement can be found. It tries its best, but it's not gonna invent a style for you. ๐Ÿ™ˆ

The following text is verbatim from the Unicode TR35-49 spec [1]. Copy-paste, not my words, but very much my energy:

  • standard: A typical 'and' list for arbitrary placeholders. eg. "January, February, and March"
  • standard-short: A short version of an 'and' list, suitable for use with short or abbreviated placeholder values. eg. "Jan., Feb., and Mar."
  • or: A typical 'or' list for arbitrary placeholders. eg. "January, February, or March"
  • or-short: A short version of an 'or' list. eg. "Jan., Feb., or Mar."
  • unit: A list suitable for wide units. eg. "3 feet, 7 inches"
  • unit-short: A list suitable for short units eg. "3 ft, 7 in"
  • unit-narrow: A list suitable for narrow units, where space on the screen is very limited. eg. "3โ€ฒ 7โ€ณ"

[1]: โ€ https://www.unicode.org/reports/tr35/tr35-49/tr35-general.html#ListPatterns

Parameters ๐ŸŽ€

  • lst โ€“ a sequence of items to format in to a list
  • style โ€“ the style to format the list with. See above for description.
  • locale โ€“ the locale. Defaults to the system locale.

Messages and Catalogs ๐Ÿ’Œ๐Ÿ“š

Babel provides functionality to work with message catalogs. This part of the API documentation shows those parts. Translation infrastructure time, let's gooo. ๐Ÿ”ฅ

Messages and Catalogs ๐Ÿ—‚๏ธ

This module provides a basic interface to hold catalog and message information. It's generally used to modify a gettext catalog but it is not being used to actually use the translations. So it's the storage unit, not the DJ. iykyk. ๐Ÿ’€

Catalogs ๐Ÿ“š

class babel.messages.catalog.Catalog(locale: Locale | str | None = None, domain: str | None = None, header_comment: str | None = '# Translations template for PROJECT.\n# Copyright (C) YEAR ORGANIZATION\n# This file is distributed under the same license as the PROJECT project.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#', project: str | None = None, version: str | None = None, copyright_holder: str | None = None, msgid_bugs_address: str | None = None, creation_date: datetime | str | None = None, revision_date: datetime | time | float | str | None = None, last_translator: str | None = None, language_team: str | None = None, charset: str | None = None, fuzzy: bool = True)

Representation of a message catalog. The whole thing. The container. The icon. ๐Ÿ‘‘

__iter__() -> Iterator[Message]

Iterates through all the entries in the catalog, in the order they were added, yielding a Message object for every entry. No skipping, no reordering, first-in-first-yielded. โœจ

Return type โ€” iterator


add(id: _MessageID, string: _MessageID | None = None, locations: Iterable[tuple[str, int]] = (), flags: Iterable[str] = (), auto_comments: Iterable[str] = (), user_comments: Iterable[str] = (), previous_id: _MessageID = (), lineno: int | None = None, context: str | None = None) -> Message

Add or update the message with the specified ID. ๐ŸŽ€

>>> catalog = Catalog()
>>> catalog.add(u'foo')
<Message ...>
>>> catalog[u'foo']
<Message u'foo' (flags: [])>

This method simply constructs a Message object with the given arguments and invokes __setitem__ with that object. Lowkey just a fancy wrapper, and that's fine, that's the vibe. ๐Ÿ’…

Parameters ๐ŸŽ€

  • id โ€“ the message ID, or a (singular, plural) tuple for pluralizable messages
  • string โ€“ the translated message string, or a (singular, plural) tuple for pluralizable messages
  • locations โ€” a sequence of (filename, lineno) tuples, so basically the receipts of where each message was spotted ๐Ÿ‘€
  • flags โ€” a set or sequence of flags
  • auto_comments โ€” a sequence of automatic comments
  • user_comments โ€” a sequence of user comments
  • previous_id โ€” the previous message ID, or a (singular, plural) tuple for pluralizable messages
  • lineno โ€” the line number where the msgid line was found in the PO file, if any
  • context โ€” the message context

check()</p> -> Iterable[tuple[Message, list[TranslationError]]]

Okay so this one runs a whole lil vibe check on all the translations in the catalog โœจ

For every message that fails validation, this method yields a (message, errors) tuple, where message is the Message object and errors is a sequence of TranslationError objects.

Return type generator of (message, errors)


delete(id: _MessageID, context: str | None = None)</p> -> None

Deletes the message with the specified ID and context. Gone. Poof. ๐Ÿ’…

Parameters

  • id โ€” the message ID
  • context โ€” the message context, or None for no context

get(id: _MessageID, context: str | None = None)</p> -> Message | None

Returns the message with the specified ID and context.

Parameters

  • id โ€” the message ID
  • context โ€” the message context, or None for no context

property header_comment</p>: str

The header comment for the catalog. Bestie, this is the lil intro paragraph at the top, and it's giving very much โœจauthorial voiceโœจ

>>> catalog = Catalog(project='Foobar', version='1.0',
...                   copyright_holder='Foo Company')
>>> print(catalog.header_comment)
# Translations template for Foobar.
# Copyright (C) ... Foo Company
# This file is distributed under the same license as the Foobar project.
# FIRST AUTHOR <EMAIL@ADDRESS>, ....
#

The header can also be set from a string. Any known upper-case variables will be replaced when the header is retrieved again:

>>> catalog = Catalog(project='Foobar', version='1.0',
...                   copyright_holder='Foo Company')
>>> catalog.header_comment = '''\
... # The POT for my really cool PROJECT project.
... # Copyright (C) 1990-2003 ORGANIZATION
... # This file is distributed under the same license as the PROJECT
... # project.
... #'''
>>> 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
# project.
#

Type unicode


is_identical(other: Catalog)</p> -> bool

Checks if catalogs are identical, taking into account messages and headers. Like a "are we the same person" test but for catalogs ๐Ÿซถ


language_team</p>

Name and email address of the language team.


last_translator</p>

Name and email address of the last translator.


property mime_headers</p>: list[tuple[str, str]]

The MIME headers of the catalog, used for the special msgid "" entry. Lowkey iconic that the empty msgid is the one holding all the metadata.

The behavior of this property changes slightly depending on whether a locale is set or not โ€” the latter indicating that the catalog is actually a template for actual translations.

Here's an example of the output for such a catalog template:

>>> from babel.dates import UTC
>>> from datetime import datetime
>>> created = datetime(1990, 4, 1, 15, 30, tzinfo=UTC)
>>> catalog = Catalog(project='Foobar', version='1.0',
...                   creation_date=created)
>>> for name, value in catalog.mime_headers:
...     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
PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
Last-Translator: FULL NAME <EMAIL@ADDRESS>
Language-Team: LANGUAGE <LL@li.org>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Generated-By: Babel ...

And here's an example of the output when the locale is set:

>>> revised = datetime(1990, 8, 3, 12, 0, tzinfo=UTC)
>>> catalog = Catalog(locale='de_DE', project='Foobar', version='1.0',
...                   creation_date=created, revision_date=revised,
...                   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))
Project-Id-Version: Foobar 1.0
Report-Msgid-Bugs-To: EMAIL@ADDRESS
POT-Creation-Date: 1990-04-01 15:30+0000
PO-Revision-Date: 1990-08-03 12:00+0000
Last-Translator: John Doe <jd@example.com>
Language: de_DE
Language-Team: de_DE <de@example.com>
Plural-Forms: nplurals=2; plural=(n != 1);
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
Generated-By: Babel ...

Type list


property num_plurals</p>: int

The number of plurals used by the catalog or locale.

>>> Catalog(locale='en').num_plurals
2
>>> Catalog(locale='ga').num_plurals
5

Type int


property plural_expr</p>: str

The plural expression used by the catalog or locale.

>>> Catalog(locale='en').plural_expr
'(n != 1)'
>>> Catalog(locale='ga').plural_expr
'(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4)'
>>> Catalog(locale='ding').plural_expr  # unknown locale
'(n != 1)'

Type str


property plural_forms</p>: str

Return the plural forms declaration for the locale.

>>> Catalog(locale='en').plural_forms
'nplurals=2; plural=(n != 1);'
>>> Catalog(locale='pt_BR').plural_forms
'nplurals=2; plural=(n > 1);'

Type str


update(template: Catalog, no_fuzzy_matching: bool = False, update_header_comment: bool = False, keep_user_comments: bool = True, update_creation_date: bool = True)</p> -> None

Update the catalog based on the given template catalog. It's giving glow-up ๐Ÿ’…โœจ

>>> from babel.messages import Catalog
>>> template = Catalog()
>>> template.add('green', locations=[('main.py', 99)])
<Message ...>
>>> template.add('blue', locations=[('main.py', 100)])
<Message ...>
>>> template.add(('salad', 'salads'), locations=[('util.py', 42)])
<Message ...>
>>> catalog = Catalog(locale='de_DE')
>>> catalog.add('blue', u'blau', locations=[('main.py', 98)])
<Message ...>
>>> catalog.add('head', u'Kopf', locations=[('util.py', 33)])
<Message ...>
>>> catalog.add(('salad', 'salads'), (u'Salat', u'Salate'),
...             locations=[('util.py', 38)])
<Message ...>

>>> catalog.update(template)
>>> len(catalog)
3

>>> msg1 = catalog['green']
>>> msg1.string
>>> msg1.locations
[('main.py', 99)]

>>> msg2 = catalog['blue']
>>> msg2.string
u'blau'
>>> msg2.locations
[('main.py', 100)]

>>> msg3 = catalog['salad']
>>> msg3.string
(u'Salat', u'Salate')
>>> msg3.locations
[('util.py', 42)]

Messages that are in the catalog but not in the template are removed from the main collection, but can still be accessed via the obsolete member. They're not gone gone, they're just in their retirement era ๐Ÿซถ

>>> 'head' in catalog
False
>>> list(catalog.obsolete.values())
[<Message 'head' (flags: [])>]

Parameters

  • template โ€” the reference catalog, usually read from a POT file
  • no_fuzzy_matching โ€” whether to use fuzzy matching of message IDs
  • update_header_comment โ€” whether to copy the header comment from the template
  • keep_user_comments โ€” whether to keep user comments from the old catalog
  • update_creation_date โ€” whether to copy the creation date from the template

๐Ÿ’ฌ Messages ๐Ÿ’ฌ

class babel.messages.catalog.Message(id: _MessageID, string: _MessageID | None = '', locations: Iterable[tuple[str, int]] = (), flags: Iterable[str] = (), auto_comments: Iterable[str] = (), user_comments: Iterable[str] = (), previous_id: _MessageID = (), lineno: int | None = None, context: str | None = None)

Representation of a single message in a catalog. Just one lil guy, living his best life ๐ŸŒธ

check(catalog: Catalog | None = None)</p> -> list[TranslationError]

Run various validation checks on the message. Some validations are only performed if the catalog is provided. This method returns a sequence of TranslationError objects.

Return type iterator

Parameters

  • catalog โ€” a catalog instance that gets passed to the checkers ๐Ÿ’…
See Catalog.check for a way to perform checks for all messages in a catalog. ๐Ÿซถ

property fuzzy: bool

Whether the translation is fuzzy. Like, is it in that "don't trust me yet" state? ๐Ÿ™ˆ

>>> Message('foo').fuzzy
False
>>> msg = Message('foo', 'foo', flags=['fuzzy'])
>>> msg.fuzzy
True
>>> msg
<Message 'foo' (flags: ['fuzzy'])>

Type: bool


is_identical(other: Message) -> bool

Checks whether messages are identical, taking into account all properties. Every single little detail, no cap. ๐Ÿ’ฏ


property pluralizable: bool

Whether the message is plurizable. Can it do the whole singular/plural thing? ๐ŸŒ€

>>> Message('foo').pluralizable
False
>>> Message(('foo', 'bar')).pluralizable
True

Type: bool


property python_brace_format: bool

Whether the message contains Python f-string parameters. You know, the {name} vibe. โœจ

>>> Message('Hello, {name}!').python_brace_format
True
>>> Message(('One apple', '{count} apples')).python_brace_format
True

Type: bool


property python_format: bool

Whether the message contains Python-style parameters. The %(name)s classic. ๐Ÿ’…

>>> Message('foo %(name)s bar').python_format
True
>>> Message(('foo %(name)s', 'foo %(name)s')).python_format
True

Type: bool


Exceptions

exception babel.messages.catalog.TranslationError

Exception thrown by translation checkers when invalid message translations are encountered. It's giving "something's wrong and we need to talk about it". ๐Ÿ˜ญ


Low-Level Extraction Interface

The low level extraction interface can be used to extract from directories or files directly. Normally this is not needed as the command line tools can do that for you. So like, only go here if you're feeling extra DIY. ๐Ÿ› ๏ธ


Extraction Functions

The extraction functions are what the command line tools use internally to extract strings. They're the backstage crew making the magic happen. ๐ŸŽฌ


babel.messages.extract.extract_from_dir

babel.messages.extract.extract_from_dir(dirname: str | os.PathLike[str] | None = None, method_map: Iterable[tuple[str, str]] = [('**.py', 'python')], options_map: SupportsItems[str, dict[str, Any]] | None = None, keywords: Mapping[str, _Keyword] = {'N_': None, '_': None, 'dgettext': (2,), 'dngettext': (2, 3), 'gettext': None, 'ngettext': (1, 2), 'npgettext': ((1, 'c'), 2, 3), 'pgettext': ((1, 'c'), 2), 'ugettext': None, 'ungettext': (1, 2)}, comment_tags: Collection[str] = (), callback: Callable[[str, str, dict[str, Any]], object] | None = None, strip_comment_tags: bool = False, directory_filter: Callable[[str], bool] | None = None) -> Generator[_FileExtractionResult, None, None]

Extract messages from any source files found in the given directory.

This function generates tuples of the form (filename, lineno, message, comments, context).

Which extraction method is used per file is determined by the method_map parameter, which maps extended glob patterns to extraction method names. For example, the following is the default mapping:

>>> method_map = [
...     ('**.py', 'python')
... ]

This basically says that files with the filename extension โ€œ.pyโ€ at any level inside the directory should be processed by the โ€œpythonโ€ extraction method. Files that donโ€™t match any of the mapping patterns are ignored. See the documentation of the pathmatch function for details on the pattern syntax.

The following extended mapping would also use the โ€œgenshiโ€ extraction method on any file in โ€œtemplatesโ€ subdirectory:

>>> method_map = [
...     ('**/templates/**.*', 'genshi'),
...     ('**.py', 'python')
... ]

The dictionary provided by the optional options_map parameter augments these mappings. It uses extended glob patterns as keys, and the values are dictionaries mapping options names to option values (both strings).

The glob patterns of the options_map do not necessarily need to be the same as those used in the method mapping. For example, while all files in the templates folders in an application may be Genshi applications, the options for those files may differ based on extension:

>>> options_map = {
...     '**/templates/**.txt': {
...         'template_class': 'genshi.template:TextTemplate',
...         'encoding': 'latin-1'
...     },
...     '**/templates/**.html': {
...         'include_attrs': ''
...     }
... }

Parameters

  • dirname โ€” the path to the directory to extract messages from. If not given the current working directory is used. ๐Ÿ“
  • method_map โ€” a list of (pattern, method) tuples that maps of extraction method names to extended glob patterns
  • options_map โ€” a dictionary of additional options (optional)
  • keywords โ€” a dictionary mapping keywords (i.e. names of functions that should be recognized as translation functions) to tuples that specify which of their arguments contain localizable strings
  • comment_tags โ€” a list of tags of translator comments to search for and include in the results
  • callback โ€” a function that is called for every file that message are extracted from, just before the extraction itself is performed; the function is passed the filename, the name of the extraction method and and the options dictionary as positional arguments, in that order
  • strip_comment_tags โ€” a flag that if set to True causes all comment tags to be removed from the collected comments.
  • directory_filter โ€” a callback to determine whether a directory should be recursed into. Receives the full directory path; should return True if the directory is valid.
See pathmatch

babel.messages.extract.extract_from_file

babel.messages.extract.extract_from_file(method: _ExtractionMethod, filename: str | os.PathLike[str], keywords: Mapping[str, _Keyword] = {'N_': None, '_': None, 'dgettext': (2,), 'dngettext': (2, 3), 'gettext': None, 'ngettext': (1, 2), 'npgettext': ((1, 'c'), 2, 3), 'pgettext': ((1, 'c'), 2), 'ugettext': None, 'ungettext': (1, 2)}, comment_tags: Collection[str] = (), options: Mapping[str, Any] | None = None, strip_comment_tags: bool = False) -> list[_ExtractionResult]

Extract messages from a specific file.

This function returns a list of tuples of the form (lineno, message, comments, context).

Parameters

  • filename โ€” the path to the file to extract messages from
  • method โ€” a string specifying the extraction method (.e.g. โ€œpythonโ€)
  • keywords โ€” a dictionary mapping keywords (i.e. names of functions that should be recognized as translation functions) to tuples that specify which of their arguments contain localizable strings
  • comment_tags โ€” a list of translator tags to search for and include in the results
  • strip_comment_tags โ€” a flag that if set to True causes all comment tags to be removed from the collected comments.
  • options โ€” a dictionary of additional options (optional)

Returns

list of tuples of the form (lineno, message, comments, context)

Return type

list[tuple[int, str|tuple[str], list[str], str|None]


babel.messages.extract.extract

babel.messages.extract.extract(method: _ExtractionMethod, fileobj: _FileObj, keywords: Mapping[str, _Keyword] = {'N_': None, '_': None, 'dgettext': (2,), 'dngettext': (2, 3), 'gettext': None, 'ngettext': (1, 2), 'npgettext': ((1, 'c'), 2, 3), 'pgettext': ((1, 'c'), 2), 'ugettext': None, 'ungettext': (1, 2)}, comment_tags: Collection[str] = (), options: Mapping[str, Any] | None = None, strip_comment_tags: bool = False) -> Generator[_ExtractionResult, None, None]

Extract messages from the given file-like object using the specified extraction method.

This function returns tuples of the form (lineno, message, comments, context).

The implementation dispatches the actual extraction to plugins, based on the value of the method parameter.

>>> source = b'''# foo module
... def run(argv):
...    print(_('Hello, world!'))
... '''

>>> from io import BytesIO
>>> for message in extract('python', BytesIO(source)):
...     print(message)
(3, u'Hello, world!', [], None)

Parameters

  • method โ€” an extraction method (a callable), or a string specifying the extraction method (.e.g. โ€œpythonโ€); if this is a simple name, the extraction function will be looked up by entry point; if it is an explicit reference to a function (of the form package.module:funcname or package.module.funcname), the corresponding function will be imported and used
  • fileobj โ€” the file-like object the messages should be extracted from
  • keywords โ€” a dictionary mapping keywords (i.e. names of functions that should be recognized as translation functions) to tuples that specify which of their arguments contain localizable strings
  • comment_tags โ€” a list of translator tags to search for and include in the results
  • options โ€” a dictionary of additional options (optional)
  • strip_comment_tags โ€” a flag that if set to True causes all comment tags to be removed from the collected comments.

Raises ValueError โ€” if the extraction method is not registered

Returns

iterable of tuples of the form (lineno, message, comments, context)

Return type

Iterable[tuple[int, str|tuple[str], list[str], str|None]


โœจ Language Parsing โœจ

okay so the language parsing functions are the little string-snatchers that pull messages out of source files ๐Ÿ’–. The extraction functions use them automatically, but sometimes itโ€™s lowkey useful to register wrapper functions, and then you can call these low-level ones directly. New functions can be registered through the setuptools entrypoint system. slay.

babel.messages.extract.extract_python(fileobj: IO[bytes], keywords: Mapping[str, _Keyword], comment_tags: Collection[str], options: _PyOptions) -> Generator[_ExtractionResult, None, None]

Extract messages from Python source code. ๐Ÿ

It returns an iterator yielding tuples in the following form (lineno, funcname, message, comments).

Parameters

  • fileobj โ€” the seekable, file-like object the messages should be extracted from
  • keywords โ€” a list of keywords (i.e. function names) that should be recognized as translation functions
  • comment_tags โ€” a list of translator tags to search for and include in the results
  • options โ€” a dictionary of additional options (optional)

Return type

iterator

babel.messages.extract.extract_javascript(fileobj: _FileObj, keywords: Mapping[str, _Keyword], comment_tags: Collection[str], options: _JSOptions, lineno: int = 1) -> Generator[_ExtractionResult, None, None]

Extract messages from JavaScript source code. โœจ

Parameters

  • fileobj โ€” the seekable, file-like object the messages should be extracted from
  • keywords โ€” a list of keywords (i.e. function names) that should be recognized as translation functions
  • comment_tags โ€” a list of translator tags to search for and include in the results
  • options โ€” a dictionary of additional options (optional) Supported options are: * jsx โ€“ set to false to disable JSX/E4X support. * template_string โ€“ if True, supports gettext(key) * parse_template_string โ€“ if True will parse the contents of javascript template strings.
  • lineno โ€” line number offset (for parsing embedded fragments)

babel.messages.extract.extract_nothing(fileobj: _FileObj, keywords: Mapping[str, _Keyword], comment_tags: Collection[str], options: Mapping[str, Any]) -> list[_ExtractionResult]

Pseudo extractor that does not actually extract anything, but simply returns an empty list. ๐Ÿ’€ itโ€™s giving โ€œIโ€™m here but Iโ€™m not doing anythingโ€ energy.


๐ŸŒธ MO File Support ๐ŸŒธ

The MO file support can read and write MO files. It reads them into Catalog objects and also writes catalogs out. ๐ŸŽ€

babel.messages.mofile.read_mo(fileobj: SupportsRead[bytes]) -> Catalog

Read a binary MO file from the given file-like object and return a corresponding Catalog object.

Parameters

  • fileobj โ€” the file-like object to read the MO file from
Note โ€” The implementation of this function is heavily based on the GNUTranslations._parse method of the gettext module in the standard library.

babel.messages.mofile.write_mo(fileobj: SupportsWrite[bytes], catalog: Catalog, use_fuzzy: bool = False) -> None

Write a catalog to the specified file-like object using the GNU MO file format.

>>> import sys
>>> from babel.messages import Catalog
>>> from gettext import GNUTranslations
>>> from io import BytesIO

>>> catalog = Catalog(locale='en_US')
>>> catalog.add('foo', 'Voh')
<Message ...>
>>> catalog.add((u'bar', u'baz'), (u'Bahr', u'Batz'))
<Message ...>
>>> catalog.add('fuz', 'Futz', flags=['fuzzy'])
<Message ...>
>>> catalog.add('Fizz', '')
<Message ...>
>>> catalog.add(('Fuzz', 'Fuzzes'), ('', ''))
<Message ...>
>>> buf = BytesIO()

>>> write_mo(buf, catalog)
>>> x = buf.seek(0)
>>> translations = GNUTranslations(fp=buf)
>>> if sys.version_info[0] >= 3:
...     translations.ugettext = translations.gettext
...     translations.ungettext = translations.ngettext
>>> 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'

Parameters

  • fileobj โ€” the file-like object to write to
  • catalog โ€” the Catalog instance
  • use_fuzzy โ€” whether translations marked as โ€œfuzzyโ€ should be included in the output

๐Ÿฆ‹ PO File Support ๐Ÿฆ‹

The PO file support can read and write PO and POT files. It reads them into Catalog objects and also writes catalogs out. ๐Ÿ’…

babel.messages.pofile.read_po(fileobj: IO[AnyStr] | Iterable[AnyStr], locale: Locale | str | None = None, domain: str | None = None, ignore_obsolete: bool = False, charset: str | None = None, abort_invalid: bool = False) -> Catalog

Read messages from a gettext PO (portable object) file from the given file-like object (or an iterable of lines) and return a Catalog.

>>> from datetime import datetime
>>> from io import StringIO
>>> buf = StringIO('''
... #: main.py:1
... #, fuzzy, python-format
... msgid "foo %(name)s"
... msgstr "quux %(name)s"
...
... # A user comment
... #. An auto comment
... #: main.py:3
... msgid "bar"
... msgid_plural "baz"
... msgstr[0] "bar"
... msgstr[1] "baaz"
... ''')
>>> catalog = read_po(buf)
>>> catalog.revision_date = datetime(2007, 4, 1)

>>> for message in catalog:
...     if message.id:
...         print((message.id, message.string))
...         print(' ', (message.locations, sorted(list(message.flags))))
...         print(' ', (message.user_comments, message.auto_comments))
(u'foo %(name)s', u'quux %(name)s')
  ([(u'main.py', 1)], [u'fuzzy', u'python-format'])
  ([], [])
((u'bar', u'baz'), (u'bar', u'baaz'))
  ([(u'main.py', 3)], [])
  ([u'A user comment'], [u'An auto comment'])
Added in version 1.0: Added support for explicit charset argument.

Parameters

  • fileobj โ€” the file-like object (or iterable of lines) to read the PO file from
  • locale โ€” the locale identifier or Locale object, or None if the catalog is not bound to a locale (which basically means itโ€™s a template)
  • domain โ€” the message domain
  • ignore_obsolete โ€” whether to ignore obsolete messages in the input
  • charset โ€” the character set of the catalog.
  • abort_invalid โ€” abort read if po file is invalid

babel.messages.pofile.write_po(fileobj: SupportsWrite[bytes], catalog: Catalog, width: int = 76, no_location: bool = False, omit_header: bool = False, sort_output: bool = False, sort_by_file: bool = False, ignore_obsolete: bool = False, include_previous: bool = False, include_lineno: bool = True) -> None

Write a gettext PO (portable object) template file for a given message catalog to the provided file-like object.

>>> catalog = Catalog()
>>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
...             flags=('fuzzy',))
<Message...>
>>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
<Message...>
>>> from io import BytesIO
>>> buf = BytesIO()
>>> write_po(buf, catalog, omit_header=True)
>>> print(buf.getvalue().decode("utf8"))
#: main.py:1
#, fuzzy, python-format
msgid "foo %(name)s"
msgstr ""

#: main.py:3
msgid "bar"
msgid_plural "baz"
msgstr[0] ""
msgstr[1] ""

Parameters

  • fileobj โ€” the file-like object to write to
  • catalog โ€” the Catalog instance
  • width โ€” the maximum line width for the generated output; use None, 0, or a negative number to completely disable line wrapping
  • no_location โ€” do not emit a location comment for every message
  • omit_header โ€” do not include the msgid "" entry at the top of the output
  • sort_output โ€” whether to sort the messages in the output by msgid
  • sort_by_file โ€” whether to sort the messages in the output by their locations
  • ignore_obsolete โ€” whether to ignore obsolete messages and not include them in the output; by default they are included as comments
  • include_previous โ€” include the old msgid as a comment when updating the catalog
  • include_lineno โ€” include line number in the location comment

๐Ÿ’ฐ Numbers and Currencies ๐Ÿ’ฐ

The number module provides functionality to format numbers for different locales. This includes arbitrary numbers as well as currency. ๐Ÿค‘

๐Ÿ”ข Number Formatting ๐Ÿ”ข

babel.numbers.format_number(number: float | Decimal | str, locale: Locale | str | None = None) -> str

Return the given number formatted for a specific locale.

>>> format_number(1099, locale='en_US')
u'1,099'
>>> format_number(1099, locale='de_DE')
u'1.099'
Deprecated since version 2.6.0: Use babel.numbers.format_decimal() instead.

Parameters

  • number โ€” the number to format
  • locale โ€” the Locale object or locale identifier. Defaults to the system numeric locale.

๐Ÿ’– babel.numbers.format_decimal ๐Ÿ’–

babel.numbers.format_decimal(number: float | Decimal | str, format: str | NumberPattern | None = None, locale: Locale | str | None = None, decimal_quantization: bool = True, group_separator: bool = True, *, numbering_system: Literal['default'] | str = 'latn') -> str

Okay bestie, this one returns the decimal number you hand it, all dressed up for a specific locale. Slay. ๐Ÿ’…

>>> format_decimal(1.2345, locale='en_US')
u'1.234'
>>> format_decimal(1.2346, locale='en_US')
u'1.235'
>>> format_decimal(-1.2346, locale='en_US')
u'-1.235'
>>> format_decimal(1.2345, locale='sv_SE')
u'1,234'
>>> format_decimal(1.2345, locale='de')
u'1,234'
>>> format_decimal(1.2345, locale='ar_EG', numbering_system='default')
u'1ูซ234'
>>> format_decimal(1.2345, locale='ar_EG', numbering_system='latn')
u'1.234'

The right thousands grouping and the right decimal separator get used per locale, like obviously:

>>> format_decimal(12345.5, locale='en_US')
u'12,345.5'

So by default, the locale is allowed to truncate and round a high-precision number by forcing its format pattern onto the decimal part. Not me explaining that twice, it's just how it is. You can bypass this behavior with the decimal_quantization parameter:

>>> format_decimal(1.2346, locale='en_US')
u'1.235'
>>> format_decimal(1.2346, locale='en_US', decimal_quantization=False)
u'1.2346'
>>> format_decimal(12345.67, locale='fr_CA', group_separator=False)
u'12345,67'
>>> format_decimal(12345.67, locale='en_US', group_separator=True)
u'12,345.67'

Parameters ๐Ÿซถ

  • number โ€“ the number to format
  • format
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • decimal_quantization โ€“ Truncate and round high-precision numbers to the format pattern. Defaults to True.
  • group_separator โ€“ Boolean to switch group separator on/off in a locale's number format.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale.


โœจ babel.numbers.format_compact_decimal โœจ

babel.numbers.format_compact_decimal(number: float | Decimal | str, *, format_type: Literal['short', 'long'] = 'short', locale: Locale | str | None = None, fraction_digits: int = 0, numbering_system: Literal['default'] | str = 'latn') -> str

Return the given decimal number formatted for a specific locale but in compact form โ€” the little abbreviated glow-up version. ๐ŸŒธ

>>> format_compact_decimal(12345, format_type="short", locale='en_US')
u'12K'
>>> format_compact_decimal(12345, format_type="long", locale='en_US')
u'12 thousand'
>>> format_compact_decimal(12345, format_type="short", locale='en_US', fraction_digits=2)
u'12.34K'
>>> format_compact_decimal(1234567, format_type="short", locale="ja_JP")
u'123ไธ‡'
>>> format_compact_decimal(2345678, format_type="long", locale="mk")
u'2 ะผะธะปะธะพะฝะธ'
>>> format_compact_decimal(21000000, format_type="long", locale="mk")
u'21 ะผะธะปะธะพะฝ'
>>> format_compact_decimal(12345, format_type="short", locale='ar_EG', fraction_digits=2, numbering_system='default')
u'12ูซ34 ุฃู„ู'

Parameters ๐ŸŽ€

  • number โ€“ the number to format
  • format_type โ€“ Compact format to use ("short" or "long")
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • fraction_digits โ€“ Number of digits after the decimal point to use. Defaults to 0.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale.


๐Ÿ’ธ babel.numbers.format_currency ๐Ÿ’ธ

babel.numbers.format_currency(number: float | Decimal | str, currency: str, format: str | NumberPattern | None = None, locale: Locale | str | None = None, currency_digits: bool = True, format_type: Literal['name', 'standard', 'accounting'] = 'standard', decimal_quantization: bool = True, group_separator: bool = True, *, numbering_system: Literal['default'] | str = 'latn') -> str

Return a formatted currency value. Money money money, but make it localized. ๐Ÿ’ฐ

>>> format_currency(1099.98, 'USD', locale='en_US')
'$1,099.98'
>>> format_currency(1099.98, 'USD', locale='es_CO')
u'US$1.099,98'
>>> format_currency(1099.98, 'EUR', locale='de_DE')
u'1.099,98\xa0\u20ac'
>>> format_currency(1099.98, 'EGP', locale='ar_EG', numbering_system='default')
u'1ูฌ099ูซ98 ุฌ.ู….'

The format can also be specified explicitly. The currency is placed with the 'ยค' sign. As the sign gets repeated the format expands (ยค being the symbol, ยคยค is the currency abbreviation and ยคยคยค is the full name of the currency):

>>> format_currency(1099.98, 'EUR', u'ยคยค #,##0.00', locale='en_US')
u'EUR 1,099.98'
>>> format_currency(1099.98, 'EUR', u'#,##0.00 ยคยคยค', locale='en_US')
u'1,099.98 euros'

Currencies usually have a specific number of decimal digits, periodt. This function favours that information over the given format:

>>> format_currency(1099.98, 'JPY', locale='en_US')
u'\xa51,100'
>>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES')
u'1.099,98'

However, the number of decimal digits can be overridden from the currency information, by setting the last parameter to False:

>>> format_currency(1099.98, 'JPY', locale='en_US', currency_digits=False)
u'\xa51,099.98'
>>> format_currency(1099.98, 'COP', u'#,##0.00', locale='es_ES', currency_digits=False)
u'1.099,98'

If a format is not specified, the type of currency format to use from the locale can be specified:

>>> format_currency(1099.98, 'EUR', locale='en_US', format_type='standard')
u'\u20ac1,099.98'

When the given currency format type is not available, an exception is raised. No cap, it just throws:

>>> format_currency('1099.98', 'EUR', locale='root', format_type='unknown')
Traceback (most recent call last):
    ...
UnknownCurrencyFormatError: "'unknown' is not a known currency format type"
>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=False)
u'$101299.98'

>>> format_currency(101299.98, 'USD', locale='en_US', group_separator=True)
u'$101,299.98'

You can also pass format_type='name' to use long display names. The order of the number and currency name, along with the correct localized plural form of the currency name, is chosen according to locale โ€” it's giving multilingual main character energy:

>>> format_currency(1, 'USD', locale='en_US', format_type='name')
u'1.00 US dollar'
>>> format_currency(1099.98, 'USD', locale='en_US', format_type='name')
u'1,099.98 US dollars'
>>> format_currency(1099.98, 'USD', locale='ee', format_type='name')
u'us ga dollar 1,099.98'

By default the locale is allowed to truncate and round a high-precision number by forcing its format pattern onto the decimal part. You can bypass this behavior with the decimal_quantization parameter:

>>> format_currency(1099.9876, 'USD', locale='en_US')
u'$1,099.99'
>>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False)
u'$1,099.9876'

Parameters ๐ŸŒˆ

  • number โ€“ the number to format
  • currency โ€“ the currency code
  • format โ€“ the format string to use
  • locale โ€“ the Locale object or locale identifier. Defaults to the system currency locale or numeric locale.
  • currency_digits โ€“ use the currency's natural number of decimal digits
  • format_type โ€“ the currency format type to use
  • decimal_quantization โ€“ Truncate and round high-precision numbers to the format pattern. Defaults to True.
  • group_separator โ€“ Boolean to switch group separator on/off in a locale's number format.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale.


๐Ÿค‘ babel.numbers.format_compact_currency ๐Ÿค‘

babel.numbers.format_compact_currency(number: float | Decimal | str, currency: str, *, format_type: Literal['short'] = 'short', locale: Locale | str | None = None, fraction_digits: int = 0, numbering_system: Literal['default'] | str = 'latn') -> str

Format a number as a currency value in compact form. Tiny money. Lil money. Ate. โœจ

>>> format_compact_currency(12345, 'USD', locale='en_US')
u'$12K'
>>> format_compact_currency(123456789, 'USD', locale='en_US', fraction_digits=2)
u'$123.46M'
>>> format_compact_currency(123456789, 'EUR', locale='de_DE', fraction_digits=1)
'123,5 Mio. โ‚ฌ'

Parameters ๐Ÿฆ‹

  • number โ€“ the number to format
  • currency โ€“ the currency code
  • format_type โ€“ the compact format type to use. Defaults to "short".
  • locale โ€“ the Locale object or locale identifier. Defaults to the system currency locale or numeric locale.
  • fraction_digits โ€“ Number of digits after the decimal point to use. Defaults to 0.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.
  • Raises UnsupportedNumberingSystemError โ€“ if the locale is literally not about that numbering system, bestie. It just can't. ๐Ÿ’€

babel.numbers.format_percent

babel.numbers.format_percent(number: float | Decimal | str, format: str
| NumberPattern | None = None, locale: Locale | str | None = None,
decimal_quantization: bool = True, group_separator: bool = True, *,
numbering_system: Literal['default'] | str = 'latn') -> str

Okay so this one hands you back a formatted percent value for whatever locale you're feeling that day. ๐Ÿ’…

>>> format_percent(0.34, locale='en_US')
u'34%'
>>> format_percent(25.1234, locale='en_US')
u'2,512%'
>>> format_percent(25.1234, locale='sv_SE')
u'2\xa0512\xa0%'
>>> format_percent(25.1234, locale='ar_EG', numbering_system='default')
u'2ูฌ512%'

And yeah, you can also just straight-up tell it the format pattern, no gatekeeping:

>>> format_percent(25.1234, u'#,##0โ€ฐ', locale='en_US')
u'25,123โ€ฐ'

Now listen โ€” by default the locale is allowed to truncate and round a high-precision number, by forcing its format pattern onto the decimal part. If you're not into that, you can bypass the whole thing with the decimal_quantization parameter:

>>> format_percent(23.9876, locale='en_US')
u'2,399%'
>>> format_percent(23.9876, locale='en_US', decimal_quantization=False)
u'2,398.76%'

>>> format_percent(229291.1234, locale='pt_BR', group_separator=False)
u'22929112%'

>>> format_percent(229291.1234, locale='pt_BR', group_separator=True)
u'22.929.112%'

Parameters ๐ŸŽ€

  • number โ€“ the percent number you wanna format
  • format
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • decimal_quantization โ€“ Truncate and round high-precision numbers to the format pattern. Defaults to True.
  • group_separator โ€“ Boolean to switch group separator on/off in a locale's number format.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.
  • Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale. (Again. She said what she said.) ๐Ÿ™ˆ

babel.numbers.format_scientific

babel.numbers.format_scientific(number: float | Decimal | str, format:
str | NumberPattern | None = None, locale: Locale | str | None = None,
decimal_quantization: bool = True, *, numbering_system:
Literal['default'] | str = 'latn') -> str

Return value formatted in scientific notation for a specific locale. Big brain energy fr fr ๐Ÿ”ฅ

>>> format_scientific(10000, locale='en_US')
u'1E4'
>>> format_scientific(10000, locale='ar_EG', numbering_system='default')
u'1ุฃุณ4'

Explicit format pattern? Say less:

>>> format_scientific(1234567, u'##0.##E00', locale='en_US')
u'1.23E06'

And same deal as before โ€” by default the locale is allowed to truncate and round a high-precision number by forcing its format pattern onto the decimal part. The decimal_quantization parameter is your escape hatch:

>>> format_scientific(1234.9876, u'#.##E0', locale='en_US')
u'1.23E3'
>>> format_scientific(1234.9876, u'#.##E0', locale='en_US', decimal_quantization=False)
u'1.2349876E3'

Parameters โœจ

  • number โ€“ the number to format
  • format
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • decimal_quantization โ€“ Truncate and round high-precision numbers to the format pattern. Defaults to True.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.
  • Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale.

๐Ÿ”ข Number Parsing ๐Ÿ”ข

babel.numbers.parse_number

babel.numbers.parse_number(string: str, locale: Locale | str | None =
None, *, numbering_system: Literal['default'] | str = 'latn') -> int

Parse a localized number string into an integer. The reverse girlie of formatting, we love a two-way street ๐Ÿซถ

>>> parse_number('1,099', locale='en_US')
1099
>>> parse_number('1.099', locale='de_DE')
1099

And when the string just refuses to be parsed? Exception. Obviously.

>>> parse_number('1.099,98', locale='de')
Traceback (most recent call last):
    ...
NumberFormatError: '1.099,98' is not a valid number

Parameters ๐ŸŒธ

  • string โ€“ the string to parse
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Returns

  • the parsed number

Raises

  • NumberFormatError โ€“ if the string can not be converted to a number
  • UnsupportedNumberingSystemError โ€“ if the numbering system is not supported by the locale.

babel.numbers.parse_decimal

babel.numbers.parse_decimal(string: str, locale: Locale | str | None =
None, strict: bool = False, *, numbering_system: Literal['default'] |
str = 'latn') -> Decimal

Parse a localized decimal string into a Decimal. Precision queen behavior, no cap ๐Ÿ’œ

>>> parse_decimal('1,099.98', locale='en_US')
Decimal('1099.98')
>>> parse_decimal('1.099,98', locale='de')
Decimal('1099.98')
>>> parse_decimal('12 345,123', locale='ru')
Decimal('12345.123')
>>> parse_decimal('1ูฌ099ูซ98', locale='ar_EG', numbering_system='default')
Decimal('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

Now โ€” if strict is set to True and the given string contains a number formatted in an irregular way, an exception is raised. She's strict and she's judging your formatting choices, lovingly:

>>> parse_decimal('30.00', locale='de', strict=True)
Traceback (most recent call last):
    ...
NumberFormatError: '30.00' is not a properly formatted decimal number. Did you mean '3.000'? Or maybe '30,00'?

>>> parse_decimal('0.00', locale='de', strict=True)
Traceback (most recent call last):
    ...
NumberFormatError: '0.00' is not a properly formatted decimal number. Did you mean '0'?

Parameters ๐Ÿฆ‹

  • string โ€“ the string to parse
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • strict โ€“ controls whether numbers formatted in a weird way are accepted or rejected
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Raises

  • NumberFormatError โ€“ if the string can not be converted to a decimal number
  • UnsupportedNumberingSystemError โ€“ if the numbering system is not supported by the locale.

๐Ÿ’€ Exceptions ๐Ÿ’€

exception babel.numbers.NumberFormatError

exception babel.numbers.NumberFormatError(message: str, suggestions:
list[str] | None = None)

Exception raised when a string cannot be parsed into a number. It's giving "that's not a number bestie" energy ๐Ÿ˜ญ

  • suggestions โ€“ a list of properly formatted numbers derived from the invalid input. The little helper angel that says "did you maybe mean this instead?" ๐ŸŽ€

๐Ÿ“ฆ Data Access ๐Ÿ“ฆ

babel.numbers.get_currency_name

babel.numbers.get_currency_name(currency: str, count: float | Decimal |
None = None, locale: Locale | str | None = None) -> str

Return the name the locale uses for the specified currency. Iconic.

>>> get_currency_name('USD', locale='en_US')
u'US Dollar'

Added in version 0.9.4.

Parameters โญ

  • currency โ€“ the currency code.
  • count โ€“ the optional count. If provided the currency name will be pluralized to that number if possible.
  • locale โ€“ the Locale object or locale identifier. Defaults to the system currency locale or numeric locale.

babel.numbers.get_currency_symbol

babel.numbers.get_currency_symbol(currency: str, locale: Locale | str |
None = None) -> str

Return the symbol the locale uses for the specified currency. The little guy. The glyph. The moment. โœจ

>>> get_currency_symbol('USD', locale='en_US')
u'$'

Parameters ๐Ÿ’–

  • currency โ€“ the currency code.
  • locale โ€“ the Locale object or locale identifier. Defaults to the system currency locale or numeric locale.

babel.numbers.get_currency_unit_pattern

babel.numbers.get_currency_unit_pattern(currency: str, count: float |
Decimal | None = None, locale: Locale | str | None = None) -> str

Return the unit pattern used for long display of a currency value for a given locale. This is a string containing {0} where the numeric part should be substituted and {1} where the currency long display name should be substituted. It's a fill-in-the-blanks situation and honestly? Obsessed. ๐Ÿงš

>>> get_currency_unit_pattern('USD', locale='en_US', count=10)
u'{0} {1}'

Added in version 2.7.0.

Parameters ๐ŸŒˆ

  • currency โ€“ the currency code.
  • count โ€“ the optional count. If provided the unit pattern for that number will be returned.
  • locale โ€“ the Locale object or locale identifier. Defaults to the system currency locale or numeric locale.

babel.numbers.get_decimal_symbol

babel.numbers.get_decimal_symbol(locale: Locale | str | None = None, *,
numbering_system: Literal['default'] | str = 'latn') -> str

Return the symbol the locale uses to separate decimal fractions. The tiny dot that holds the whole number together. Main character, honestly. ๐Ÿ’…

>>> get_decimal_symbol('en_US')
u'.'
>>> get_decimal_symbol('ar_EG', numbering_system='default')
u'ูซ'
>>> get_decimal_symbol('ar_EG', numbering_system='latn')
u'.'

Parameters ๐Ÿ‘‘

  • locale โ€” the Locale object or the locale identifier string, bestie. Defaults to the system's numeric locale, so if you don't say anything it just picks for you. ๐Ÿ’…
  • numbering_system โ€” which numbering system we're pulling the symbol from. Defaults to "latn". And the lil special value "default" means "use whatever numbering system this locale uses by default". โœจ
Raises UnsupportedNumberingSystemError โ€” if the locale is just not having that numbering system, like at all. ๐Ÿ’€

babel.numbers.get_plus_sign_symbol(locale: Locale | str | None = None, *, numbering_system: Literal['default'] | str = 'latn') -> str

Returns the plus sign symbol your current locale is using, periodt.

>>> get_plus_sign_symbol('en_US')
u'+'
>>> get_plus_sign_symbol('ar_EG', numbering_system='default')
u'+'
>>> get_plus_sign_symbol('ar_EG', numbering_system='latn')
u'+'

Parameters

  • locale โ€” the Locale object or locale identifier. Defaults to the system numeric locale. ๐Ÿซถ
  • numbering_system โ€” the numbering system used for fetching the symbol. Defaults to "latn". The special value "default" will use the default numbering system of the locale.
Raises UnsupportedNumberingSystemError โ€” if the numbering system is not supported by the locale. ๐Ÿ˜ญ

babel.numbers.get_minus_sign_symbol(locale: Locale | str | None = None, *, numbering_system: Literal['default'] | str = 'latn') -> str

Returns the plus sign symbol used by the current locale. (yes bestie, the doc says "plus" โ€” we are reporting it exactly as written, no cap, do not come for me ๐Ÿ™ˆ)

>>> get_minus_sign_symbol('en_US')
u'-'
>>> get_minus_sign_symbol('ar_EG', numbering_system='default')
u'-'
>>> get_minus_sign_symbol('ar_EG', numbering_system='latn')
u'-'

Parameters

  • locale โ€” the Locale object or locale identifier. Defaults to the system numeric locale. ๐Ÿซถ
  • numbering_system โ€” the numbering system used for fetching the symbol. Defaults to "latn". The special value "default" will use the default numbering system of the locale.
Raises UnsupportedNumberingSystemError โ€” if the numbering system is not supported by the locale. ๐Ÿ˜ญ

babel.numbers.get_territory_currencies

Two overloads, two moods, same function ๐Ÿ’–

babel.numbers.get_territory_currencies(territory: str, start_date: date
| None = None, end_date: date | None = None, tender: bool = True,
non_tender: bool = False, include_details: Literal[False] = False) ->
list[str]
babel.numbers.get_territory_currencies(territory: str, start_date: date
| None = None, end_date: date | None = None, tender: bool = True,
non_tender: bool = False, include_details: Literal[True] = False) ->
list[dict[str, Any]]

Returns the list of currencies for the given territory that are valid for the given date range. On top of that, the currency database is out here distinguishing between tender and non-tender currencies โ€” and by default, only tender ones come back. ๐Ÿ’ธ

The return value is a list of all currencies, roughly ordered by the time of when the currency became active. The longer a currency has been in use, the further to the left of the list it sits. Main character gets the front seat, basically. ๐Ÿ‘‘

The start date defaults to today. If no end date is given, it'll be the same as the start date. Otherwise you can define a whole range. For instance, this is how you find the currencies that were in use in Austria between 1995 and 2011:

>>> from datetime import date
>>> get_territory_currencies('AT', date(1995, 1, 1), date(2011, 1, 1))
['ATS', 'EUR']

And likewise it's also possible to find all the currencies in use on a single date:

>>> get_territory_currencies('AT', date(1995, 1, 1))
['ATS']
>>> get_territory_currencies('AT', date(2011, 1, 1))
['EUR']

By default the return value only includes tender currencies. But bestie, that can be changed:

>>> get_territory_currencies('US')
['USD']
>>> get_territory_currencies('US', tender=False, non_tender=True,
...                          start_date=date(2014, 1, 1))
['USN', 'USS']

Added in version 2.0. โœจ

Parameters

  • territory โ€” the name of the territory to find the currency for. ๐ŸŒ
  • start_date โ€” the start date. If it's not given, today is assumed. ๐Ÿ“…
  • end_date โ€” the end date. If it's not given, the start date is assumed.
  • tender โ€” controls whether tender currencies should be included.
  • non_tender โ€” controls whether non-tender currencies should be included.
  • include_details โ€” if set to True, instead of returning currency codes, the return value will be dictionaries with detail information. In that case each dictionary will have the keys 'currency', 'from', 'to', and 'tender'. ๐ŸŽ€

Pluralization Support

The pluralization support provides functionality around the CLDR pluralization rules. It can parse and evaluate pluralization rules, as well as convert them to other formats such as gettext. It's giving multilingual excellence, fr fr. ๐ŸŒˆ


Basic Interface

class babel.plural.PluralRule(rules: Mapping[str, str] | Iterable[tuple[str, str]])

Represents a set of language pluralization rules. The constructor accepts a list of (tag, expr) tuples or a dict of CLDR rules. The resulting object is callable, and it accepts one parameter โ€” a positive or negative number (both integer and float are fine) โ€” for the number that indicates the plural form for a string, and it returns the tag for the format:

>>> rule = PluralRule({'one': 'n is 1'})
>>> rule(1)
'one'
>>> rule(2)
'other'

Currently the CLDR defines these tags: zero, one, two, few, many and other โ€” where other is an implicit default. Rules should be mutually exclusive; for a given numeric value, only one rule should apply (i.e. the condition should only be true for one of the plural rule elements). One tag per number, we don't do chaos here ๐Ÿ’…


classmethod parse(rules: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> PluralRule

Creates a PluralRule instance for the given rules. And if the rules you hand it are already a PluralRule object, that same object is returned. Reuse queen. ๐Ÿ‘‘

Parameters

  • rules โ€” the rules as list or dict, or a PluralRule object.
Raises RuleError โ€” if the expression is malformed. ๐Ÿ˜ญ

property rules: Mapping[str, str]

The PluralRule as a dict of unicode plural rules. ๐Ÿฆ‹

>>> rule = PluralRule({'one': 'n is 1'})
>>> rule.rules
{'one': 'n is 1'}

property tags: frozenset[str]

A set of explicitly defined tags in this rule. The implicit default 'other' rules is not part of this set unless there is an explicit rule for it. Sneaky lil implicit one, not invited unless it RSVPs. ๐Ÿ™ˆ


Conversion Functionality

babel.plural.to_javascript(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> str

Converts a list/dict of rules or a PluralRule object into a JavaScript function. This function depends on no external library at all โ€” self-sufficient icon: โœจ

>>> to_javascript({'one': 'n is 1'})
"(function(n) { return (n == 1) ? 'one' : 'other'; })"

Implementation detail: The function generated will probably evaluate expressions involved into range operations multiple times. This has the advantage that external helper functions are not required and is not a big performance hit for these simple calculations. ๐Ÿซถ

Parameters

  • rule โ€” the rules as list or dict, or a PluralRule object.
Raises RuleError โ€” if the expression is malformed. ๐Ÿ’€

babel.plural.to_python(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> Callable[[float | Decimal], str]

Converts a list/dict of rules or a PluralRule object into a regular Python function. This is useful in situations where you need a real function and don't care about the actual rule object:

>>> func = to_python({'one': 'n is 1', 'few': 'n in 2..4'})
>>> func(1)
'one'
>>> func(3)
'few'
>>> func = to_python({'one': 'n in 1,11', 'few': 'n in 3..10,13..19'})
>>> func(11)
'one'
>>> func(15)
'few'

Parameters

  • rule โ€” the rules as list or dict, or a PluralRule object.
Raises RuleError โ€” if the expression is malformed. ๐Ÿ’€

babel.plural.to_gettext(rule: Mapping[str, str] | Iterable[tuple[str, str]] | PluralRule) -> str

The plural rule as a gettext expression. Just so we're clear: the gettext expression is technically limited to integers and returns indices rather than tags. That's the vibe, that's the constraint. ๐ŸŽ€

>>> to_gettext({'one': 'n is 1', 'two': 'n is 2'})
'nplurals=3; plural=((n == 1) ? 0 : (n == 2) ? 1 : 2);'

Parameters

  • rule โ€” the rules as list or dict, or a PluralRule object.
Raises RuleError โ€” if the expression is malformed. ๐Ÿ’€

General Support Functionality

Babel ships a few general helpers that are not being used by Babel itself, but they're super useful in combination with functionality provided by it. Little freelancers. ๐Ÿ’–


Convenience Helpers

class babel.support.Format(locale: Locale | str, tzinfo: _datetime.tzinfo | None = None, *, numbering_system: Literal['default'] | str = 'latn')

A wrapper class providing the various date and number formatting functions, all bound to a specific locale and time-zone. It's literally the bestie who handles everything for you. ๐Ÿซถ

>>> from babel.util import UTC
>>> from datetime import date
>>> fmt = Format('en_US', UTC)
>>> fmt.date(date(2007, 4, 1))
u'Apr 1, 2007'
>>> fmt.decimal(1.2345)
u'1.234'

compact_currency(number: float | Decimal | str, currency: str, format_type: Literal['short'] = 'short', fraction_digits: int = 0) -> str

Returns a number in the given currency formatted for the locale, using the compact number format. Short and cute, no giant wall of digits. ๐Ÿ’ธโœจ

>>> Format('en_US').compact_currency(1234567, "USD", format_type='short', fraction_digits=2)
'$1.23M'

compact_decimal(number: float | Decimal | str, format_type: Literal['short', 'long'] = 'short', fraction_digits: int = 0) -> str

okay bestie this one returns a number formatted in compact form for the locale โ€” like, the โœจabbreviatedโœจ vibe, not the whole entire digits situation.

>>> fmt = Format('en_US')
>>> fmt.compact_decimal(123456789)
u'123M'
>>> fmt.compact_decimal(1234567, format_type='long', fraction_digits=2)
'1.23 million'

currency(number: float | Decimal | str, currency: str) -> str

Return a number in the given currency, formatted for the locale. ๐Ÿ’ธ money money money.

date(date: _datetime.date | None = None, format: _PredefinedTimeFormat | str = 'medium') -> str

Return a date formatted according to the given pattern. ๐ŸŽ€

>>> from datetime import date
>>> fmt = Format('en_US')
>>> fmt.date(date(2007, 4, 1))
u'Apr 1, 2007'

datetime(datetime: _datetime.date | None = None, format: _PredefinedTimeFormat | str = 'medium') -> str

Return a date and time formatted according to the given pattern. the way it does both at once?? iconic.

>>> from datetime import datetime
>>> from babel.dates import get_timezone
>>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
>>> fmt.datetime(datetime(2007, 4, 1, 15, 30))
u'Apr 1, 2007, 11:30:00 AM'

decimal(number: float | Decimal | str, format: str | None = None) -> str

Return a decimal number formatted for the locale. ๐Ÿ“

>>> fmt = Format('en_US')
>>> fmt.decimal(1.2345)
u'1.234'

number(number: float | Decimal | str) -> str

Return an integer number formatted for the locale. whole numbers only, no crumbs.

>>> fmt = Format('en_US')
>>> fmt.number(1099)
u'1,099'

percent(number: float | Decimal | str, format: str | None = None) -> str

Return a number formatted as a percentage for the locale. ๐Ÿ’…

>>> fmt = Format('en_US')
>>> fmt.percent(0.34)
u'34%'

scientific(number: float | Decimal | str) -> str

Return a number formatted using scientific notation for the locale. yes bestie, we're doing science today ๐Ÿงชโœจ

time(time: _datetime.time | _datetime.datetime | None = None, format: _PredefinedTimeFormat | str = 'medium') -> str

Return a time formatted according to the given pattern. โฐ

>>> from datetime import datetime
>>> from babel.dates import get_timezone
>>> fmt = Format('en_US', tzinfo=get_timezone('US/Eastern'))
>>> fmt.time(datetime(2007, 4, 1, 15, 30))
u'11:30:00 AM'

timedelta(delta: _datetime.timedelta | int, granularity: Literal['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] = 'second', threshold: float = 0.85, format: Literal['narrow', 'short', 'medium', 'long'] = 'long', add_direction: bool = False) -> str

Return a time delta according to the rules of the given locale. ๐Ÿฆ‹ the "3 months ago" pipeline, fr fr.

>>> from datetime import timedelta
>>> fmt = Format('en_US')
>>> fmt.timedelta(timedelta(weeks=11))
u'3 months'

class babel.support.LazyProxy(func: Callable[[...], Any], *args: Any, enable_cache: bool = True, **kwargs: Any)

Class for proxy objects that delegate to a specified function to evaluate the actual object. it's giving "we'll get to it when we get to it" energy and honestly? slay.

>>> def greeting(name='world'):
...     return 'Hello, %s!' % name
>>> lazy_greeting = LazyProxy(greeting, name='Joe')
>>> print(lazy_greeting)
Hello, Joe!
>>> u'  ' + lazy_greeting
u'  Hello, Joe!'
>>> u'(%s)' % lazy_greeting
u'(Hello, Joe!)'

This can be used, for example, to implement lazy translation functions that delay the actual translation until the string is actually used. The rationale for such behavior is that the locale of the user may not always be available. In web applications, you only know the locale when processing a request. ๐Ÿ™ˆ (the request said "no thoughts, head empty" and then the locale showed up late.)

The proxy implementation attempts to be as complete as possible, so that the lazy objects should mostly work as expected, for example for sorting:

>>> greetings = [
...     LazyProxy(greeting, 'world'),
...     LazyProxy(greeting, 'Joe'),
...     LazyProxy(greeting, 'universe'),
... ]
>>> greetings.sort()
>>> for greeting in greetings:
...     print(greeting)
Hello, Joe!
Hello, universe!
Hello, world!

๐ŸŒˆ Gettext Support ๐ŸŒˆ

class babel.support.Translations(fp: gettext._TranslationsReader | None = None, domain: str | None = None)

An extended translation catalog class. the upgrade we all deserved ๐Ÿ’–


add(translations: Translations, merge: bool = True)

Add the given translations to the catalog.

If the domain of the translations is different than that of the current catalog, they are added as a catalog that is only accessible by the various d*gettext functions.

Parameters

  • translations โ€“ the Translations instance with the messages to add
  • merge โ€“ whether translations for message domains that have already been added should be merged with the existing translations

classmethod load(dirname: str | PathLike[str] | None = None, locales: Iterable[str | Locale] | Locale | str | None = None, domain: str | None = None) -> NullTranslations

Load translations from the given directory. ๐Ÿ“‚โœจ

Parameters

  • dirname โ€“ the directory containing the MO files
  • locales โ€“ the list of locales in order of preference (items in this list can be either Locale objects or locale strings)
  • domain โ€“ the message domain (default: 'messages')

merge(translations: Translations)

Merge the given translations into the catalog.

Message translations in the specified catalog override any messages with the same identifier in the existing catalog. periodt.

Parameters

  • translations โ€“ the Translations instance with the messages to merge

๐Ÿฆ‹ Units ๐Ÿฆ‹

The unit module provides functionality to format measurement units for different locales. yes bestie we do metric AND imperial, we contain multitudes ๐Ÿ’…

babel.units.format_unit(value: str | float | Decimal, measurement_unit: str, length: Literal['short', 'long', 'narrow'] = 'long', format: str | None = None, locale: Locale | str | None = None, *, numbering_system: Literal['default'] | str = 'latn') -> str

Format a value of a given unit.

Values are formatted according to the locale's usual pluralization rules and number formats. (the pluralization rules are the main characters here, honestly.)

>>> format_unit(12, 'length-meter', locale='ro_RO')
u'12 metri'
>>> format_unit(15.5, 'length-mile', locale='fi_FI')
u'15,5 mailia'
>>> format_unit(1200, 'pressure-millimeter-ofhg', locale='nb')
u'1\xa0200 millimeter kvikks\xf8lv'
>>> format_unit(270, 'ton', locale='en')
u'270 tons'
>>> format_unit(1234.5, 'kilogram', locale='ar_EG', numbering_system='default')
u'1ูฌ234ูซ5 ูƒูŠู„ูˆุบุฑุงู…'

Number formats may be overridden with the format parameter. ๐Ÿ‘‘

>>> import decimal
>>> format_unit(decimal.Decimal("-42.774"), 'temperature-celsius', 'short', format='#.0', locale='fr')
u'-42,8\u202f\xb0C'

The locale's usual pluralization rules are respected. ๐Ÿซถ

>>> format_unit(1, 'length-meter', locale='ro_RO')
u'1 metru'
>>> format_unit(0, 'length-mile', locale='cy')
u'0 mi'
>>> format_unit(1, 'length-mile', locale='cy')
u'1 filltir'
>>> format_unit(3, 'length-mile', locale='cy')
u'3 milltir'

>>> format_unit(15, 'length-horse', locale='fi')
Traceback (most recent call last):
    ...
UnknownUnitError: length-horse is not a known unit in fi
ngl bestie, length-horse is not a unit. I know. I'm sad too. ๐Ÿ˜ญ

Added in version 2.2.0. โญ

Parameters

  • value โ€“ the value to format. If this is a string, no number formatting will be attempted.
  • measurement_unit โ€“ the code of a measurement unit. Known units can be found in the CLDR Unit Validity XML file: https://unicode.org/repos/cldr/tags/latest/common/validity/unit.xml
  • length โ€“ "short", "long" or "narrow"
  • format โ€“ An optional format, as accepted by format_decimal.
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • numbering_system โ€“ The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale.

Raises UnsupportedNumberingSystemError โ€“ If the numbering system is not supported by the locale.


babel.units.format_compound_unit(numerator_value: str | float | Decimal, numerator_unit: str | None = None, denominator_value: str | float | Decimal = 1, denominator_unit: str | None = None, length: Literal['short', 'long', 'narrow'] = 'long', format: str | None = None, locale: Locale | str | None = None, *, numbering_system: Literal['default'] | str = 'latn') -> str | None

Format a compound number value, i.e. "kilometers per hour" or similar. the "per" girlies stay winning ๐Ÿ”ฅ

Both unit specifiers are optional to allow for formatting of arbitrary values still according to the locale's general "per" formatting specifier. ๐Ÿ’œ

>>> format_compound_unit(7, denominator_value=11, length="short", locale="pt")
'7/11'

>>> format_compound_unit(150, "kilometer", denominator_unit="hour", locale="sv")
'150 kilometer per timme'

>>> format_compound_unit(150, "kilowatt", denominator_unit="year", locale="fi")
'150 kilowattia / vuosi'

>>> format_compound_unit(32.5, "ton", 15, denominator_unit="hour", locale="en")
'32.5 tons per 15 hours'

okay so here's what it actually looks like when you run it ๐Ÿ’…โœจ

>>> format_compound_unit(1234.5, "ton", 15, denominator_unit="hour", locale="ar_EG", numbering_system="arab")
'1ูฌ234ูซ5 ุทู† ู„ูƒู„ 15 ุณุงุนุฉ'

>>> format_compound_unit(160, denominator_unit="square-meter", locale="fr")
'160 par m\xe8tre carr\xe9'

>>> format_compound_unit(4, "meter", "ratakisko", length="short", locale="fi")
'4 m/ratakisko'

>>> format_compound_unit(35, "minute", denominator_unit="nautical-mile", locale="sv")
'35 minuter per nautisk mil'

>>> from babel.numbers import format_currency
>>> format_compound_unit(format_currency(35, "JPY", locale="de"), denominator_unit="liter", locale="de")
'35\xa0\xa5 pro Liter'

See https://www.unicode.org/reports/tr35/tr35-general.html#perUnitPatterns

Parameters

  • numerator_value โ€“ the numerator value bestie ๐Ÿ’– this may be a string, and if it is, it's treated as preformatted and the unit just gets ignored, no cap.
  • numerator_unit โ€“ the numerator unit. See format_unit.
  • denominator_value โ€“ the denominator value. This may also be a string, and same deal: if it is, it's considered preformatted and the unit is ignored.
  • denominator_unit โ€“ the denominator unit. See format_unit.
  • length โ€“ the formatting length. "short", "long" or "narrow".
  • format โ€“ an optional format, exactly as accepted by format_decimal.
  • locale โ€“ the Locale object or locale identifier. Defaults to the system numeric locale.
  • numbering_system โ€“ the numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale. ๐ŸŽ€

Returns

A formatted compound value. slay.

Raises

UnsupportedNumberingSystemError โ€“ if the numbering system is not supported by the locale. ๐Ÿ˜ญ


babel.units.get_unit_name

babel.units.get_unit_name(measurement_unit: str, length: Literal['short', 'long', 'narrow'] = 'long', locale: Locale | str | None = None) -> str | None

Get the display name for a measurement unit in the given locale. it's giving very useful ๐Ÿฆ‹

>>> get_unit_name("radian", locale="en")
'radians'

Unknown units will raise exceptions, and she is not subtle about it:

>>> get_unit_name("battery", locale="fi")
Traceback (most recent call last):
    ...
UnknownUnitError: battery/long is not a known unit/length in fi

Parameters

Returns

The unit display name, or None. (yes, sometimes she just gives you nothing, and that's valid ๐Ÿ’€)


๐ŸŒˆ ADDITIONAL NOTES ๐ŸŒˆ

๐Ÿ’– Babel Development

Babel as a library has a long history that goes back to the Trac project. Since then it has evolved into an independently developed project that implements data access for the https://cldr.unicode.org Unicode CLDR project_ โœจ

This document tries to explain as best as possible the general rules of the project in case you want to help out developing. (pls do, we love you ๐Ÿซถ)

๐ŸŽ€ Tracking the CLDR

Generally the goal of the project is to work as closely as possible with the https://cldr.unicode.org/index/charts CLDR data_ . This has in the past caused some frustrating problems because the data is entirely out of our hand ๐Ÿ˜ญ. To minimize the frustration we generally deal with CLDR updates the following way:

  • Bump the CLDR data only with a major release of Babel.
  • Never perform custom bugfixes on the CLDR data.
  • Never work around CLDR bugs within Babel. If you find a problem in the data, report it upstream.
  • Adjust the parsing of the data as soon as possible, otherwise this will spiral out of control later. This is especially the case for bigger updates that change pluralization and more.
  • Try not to test against specific CLDR data that is likely to change.

๐Ÿ Python Versions

At the moment the following Python versions should be supported:

  • Python 3.8 and up
  • PyPy 3.8 and up

๐ŸŒธ Unicode

Unicode is a big deal in Babel. Here is how the rules are set up:

  • internally everything is unicode that makes sense to have as unicode.
  • Encode / decode at boundaries explicitly. Never assume an encoding in a way it cannot be overridden. utf-8 should be generally considered the default encoding.

๐Ÿฆ‹ Dates and Timezones

Babel's timezone support relies on either pytz or zoneinfo; if pytz is installed, it is preferred over zoneinfo. Babel should assume that any timezone objects can be from either of these modules.

Assumptions to make:

  • use UTC where possible.
  • be super careful with local time. Do not use local time without knowing the exact timezone.
  • time without date is a very useless construct. Do not try to support timezones for it. If you do, assume that the current local date is assumed and not utc date.

๐Ÿ‘‘ Babel Changelog ๐Ÿ‘‘

โญ Version 2.18.0

Happy 2026! This release is, coincidentally, also being made from FOSDEM. ๐ŸŽ‰

We will aspire for a slightly less glacial release cadence in this year; there are interesting features in the pipeline. ๐Ÿ‘€

Features

  • Core: Add babel.core.get_cldr_version() by @akx in #1242
  • Core: Use CLDR 47 by @tomasr8 in #1210
  • Core: Use canonical IANA zone names in zone_territories by @akx in #1220
  • Messages: Improve extract performance via ignoring directories early during os.walk by @akx in #968
  • Messages: Merge in per-format keywords and auto_comments by @akx in #1243
  • Messages: Update keywords for extraction of dpgettext and dnpgettext by @mardiros in #1235
  • Messages: Validate all plurals in Python format checker by @tomasr8 in #1188
  • Time: Use standard library timezone instead of FixedOffsetTimezone by @akx in #1203

Bugfixes

  • Core: Fix formatting for โ€œEmpty locale identifierโ€ exception added in #1164 by @akx in #1184
  • Core: Improve handling of no-inheritance-marker in timezone data by @akx in #1194
  • Core: Make the number pattern regular expression more efficient by @akx in #1213
  • Messages: Keep translator comments next to the translation function call by @akx in #1196
  • Numbers: Fix KeyError that occurred when formatting compact currencies of exactly one thousand in several locales by @bartbroere in #1246

Other improvements

  • Core: Avoid unnecessary uses of map() by @akx in #1180
  • Messages: Have init-catalog create directories too by @akx in #1244
  • Messages: Optimizations for read_po by @akx in #1200
  • Messages: Use pathlib.Path() in catalog frontend; improve test coverage by @akx in #1204

Infrastructure and documentation

  • CI: Renovate CI & lint tools by @akx in #1228
  • CI: Tighten up CI with Zizmor by @akx in #1230
  • CI: make job permissions explicit by @akx in #1227
  • Docs: Add SECURITY.md by @akx in #1229
  • Docs: Remove u string prefix from docs by @verhovsky in #1174
  • Docs: Update dates.rst with current unicode.org tr35 link by @clach04 in #1189
  • General: Add some PyPI classifiers by @tomasr8 in #1186
  • General: Apply reformatting by hand and with Ruff by @akx in #1202
  • General: Test on and declare support for Python 3.14 by @akx in #1233
  • Tests: Convert Unittest testcases with setup/teardown to fixtures by @akx in #1240
  • Tests: Mark PyPy CI flake as xfail by @akx in #1197
  • Tests: Move pytest config to pyproject.toml by @tomasr8 in #1187
  • Tests: Unwrap most unittest test cases to bare functions by @akx in #1241

๐Ÿ’œ Version 2.17.0

Happy 2025! This release is being made from FOSDEM 2025, in Brussels, Belgium. ๐Ÿ‡ง๐Ÿ‡ช

Thank you to all contributors, new and old, and here's to another great year of internationalization and localization! ๐ŸŒ

Features

  • CLDR: Babel now uses CLDR 46, by @tomasr8 in #1145
  • Dates: Allow specifying an explicit format in parse_date/parse_time by @tomasr8 in #1131
  • Dates: More alternate characters are now supported by format_skeleton. By @tomasr8 in #1122
  • Dates: Support short and narrow formats for format_timedelta when using add_direction, by @akx in #1163
  • Messages: .po files now enclose white spaces in filenames like GNU gettext does. By @Dunedan in #1105, and @tomasr8 in #1120
  • Messages: Initial support for Message.python_brace_format, by @tomasr8 in #1169
  • Numbers: LC_MONETARY is now preferred when formatting currencies, by @akx in #1173

Bugfixes

  • Dates: Make seconds optional in parse_time time formats by @tomasr8 in #1141
  • Dates: Replace str.index with str.find by @tomasr8 in #1130
  • Dates: Strip extra leading slashes in /etc/localtime by @akx in #1165
  • Dates: Week numbering and formatting of dates with week numbers was repaired by @jun66j5 in #1179
  • General: Improve handling for locale=None by @akx in #1164
  • General: Remove redundant assignment in Catalog.__setitem__ by @tomasr8 in #1167
  • Messages: Fix extracted lineno with nested calls, by @dylankiss in #1126
  • Messages: Fix of list index out of range when translations is empty, by @gabe-sherman in #1135
  • Messages: Fix the way obsolete messages are stored by @tomasr8 in #1132
  • Messages: Simplify read_mo logic regarding catalog.charset by @tomasr8 in #1148
  • Messages: Use the first matching method & options, rather than first matching method & last options, by @jpmckinney in #1121

Deprecation and compatibility

  • Dates: Fix deprecation warnings for datetime.utcnow() by @tomasr8 in #1119
  • Docs: Adjust docs/conf.py to add compatibility with sphinx 8 by @hrnciar in #1155
  • General: Import Literal from the typing module by @tomasr8 in #1175
  • General: Replace OrderedDict with just dict by @tomasr8 in #1149
  • Messages: Mark wraptext deprecated; use TextWrapper directly in write_po by @akx in #1140

Infrastructure

  • Add tzdata as dev dependency and sync with tox.ini by @wandrew004 in #1159
  • Duplicate test code was deleted by @mattdiaz007 in #1138
  • Increase test coverage of the python_format checker by @tomasr8 in #1176
  • Small cleanups by @akx in #1160, #1166, #1170 and #1172 โœจ
  • Update CI to use python 3.13 and Ubuntu 24.04 by @tomasr8 in #1153 ๐Ÿ

Version 2.16.0 ๐ŸŽ€

Features ๐Ÿ’–

  • CLDR: Upgrade to CLDR 45 by @tomasr8 in #1077 โ€” bestie we are on CLDR 45 now, the glow-up is real ๐ŸŒโœจ
  • Lists: Support list format fallbacks by @akx in #1099 ๐Ÿ“‹
  • Messages: Initial support for reading mapping configuration as TOML by @akx in #1108 ๐Ÿ—‚๏ธ

Bugfixes ๐Ÿฉน

  • CLDR: Do not allow substituting alternates or drafts in derived locales by @akx in #1113 ๐Ÿšซ
  • Core: Allow falling back to modifier-less locale data by @akx in #1104
  • Core: Allow use of importlib.metadata for finding entrypoints by @akx in #1102
  • Dates: Avoid crashing on importing localtime when TZ is malformed by @akx in #1100 ๐Ÿ˜ญ (the malformed-TZ jump scare is no more)
  • Messages: Allow parsing .po files that have an extant but empty Language header by @akx in #1101
  • Messages: Fix --ignore-dirs being incorrectly read (#1094) by @john-psina and @Edwin18 in #1052 and #1095
  • Messages: Make pgettext search plurals when translation is not found by @tomasr8 in #1085

Infrastructure ๐Ÿ—๏ธ

  • Replace deprecated ast.Str with ast.Constant by @tomasr8 in #1083
  • CI fixes by @akx in #1080, #1097, #1103, #1107
  • Test on Python 3.13 beta releases by @akx in
  • Normalize package name to lower-case in setup.py by @akx in #1110

Documentation ๐Ÿ“š

  • Add a mention to the docs that format_skeleton(โ€ฆ, fuzzy=True) may raise by @tomasr8 in #1106
  • Two hyperlinks (to CLDR) and some typos by @buhtz in #1115

Version 2.15.0 ๐Ÿฆ‹

Python version support ๐Ÿ

  • Babel 2.15.0 will require Python 3.8 or newer. (#1048)

Features ๐Ÿ’–

  • CLDR: Upgrade to CLDR 44 (#1071) (@akx) ๐ŸŒ
  • Dates: Support for the "fall back to short format" logic for time delta formatting (#1075) (@akx)
  • Message: More versatile .po IO functions (#1068) (@akx)
  • Numbers: Improved support for alternate spaces when parsing numbers (#1007) (@ronnix's first contribution) ๐ŸŽ‰

Infrastructure ๐Ÿ—๏ธ

  • Upgrade GitHub Actions (#1054) (@cclauss's first contribution) ๐ŸŽ‰
  • The Unicode license is now included in locale-data and in the documentation (#1074) (@akx) ๐Ÿ“œ

Version 2.14.0 ๐ŸŒธ

Upcoming deprecation โš ๏ธ

  • This version, Babel 2.14, is the last version of Babel to support Python 3.7. Babel 2.15 will require Python 3.8 or newer.
  • We had previously announced Babel 2.13 to have been the last version to support Python 3.7, but being able to use CLDR 43 with Python 3.7 was deemed important enough to keep supporting the EOL Python version for one more release. ๐Ÿ’€ (rip 3.7, but we gave it one last hangout)

Possibly backwards incompatible changes ๐Ÿšจ

  • Locale.number_symbols will now have first-level keys for each numbering system. Since the implicit default numbering system still is "latn", what had previously been e.g. Locale.number_symbols['decimal'] is now Locale.number_symbols['latn']['decimal']. ๐Ÿ‘€
  • Babel no longer directly depends on either distutils or setuptools; if you had been using the Babel setuptools command extensions, you would need to explicitly depend on setuptools โ€“ though given you're running setup.py you probably already do. ๐Ÿ˜…

Features ๐Ÿ’–

  • CLDR/Numbers: Add support of local numbering systems for number symbols by @kajte in #1036
  • CLDR: Upgrade to CLDR 43 by @rix0rrr in #1043 ๐ŸŒ
  • Frontend: Allow last_translator to be passed as an option to extract_message by @AivGitHub in #1044
  • Frontend: Decouple pybabel CLI frontend from distutils/setuptools by @akx in #1041
  • Numbers: Improve parsing of malformed decimals by @Olunusib and @akx in #1042

Infrastructure ๐Ÿ—๏ธ

  • Enforce trailing commas (enable Ruff COM rule and autofix) by @akx in #1045 โ€” commas everywhere, no cap ๐Ÿซถ
  • CI: use GitHub output formats by @akx in #1046

Version 2.13.1 ๐Ÿฉน

This is a patch release to fix a few bugs.

Fixes

  • Fix a typo in _locales_to_names by @Dl84 in #1038 (issue #1037)
  • Fix setuptools dependency for Python 3.12 by @opryprin in #1033

Version 2.13.0 ๐Ÿ’…

Upcoming deprecation (reverted) โ†ฉ๏ธ

  • It was previously announced that this version, Babel 2.13, would be the last version of Babel to support Python 3.7. Babel 2.14 will still support Python 3.7. (the deprecation said sike ๐Ÿ˜ญ)

Features ๐Ÿ’–

  • Add flag to ignore POT-Creation-Date for updates by @joeportela in #999
  • Support 't' specifier in keywords by @jeanas in #1015
  • Add f-string parsing for Python 3.12 (PEP 701) by @encukou in #1027

Fixes

  • Various typing-related fixes by @akx in #979, in #978, #981, #983
  • babel.messages.catalog: deduplicate _to_fuzzy_match_key logic by @akx in #980
  • Freeze format_time() tests to a specific date to fix test failures by @mgorny in #998
  • Spelling and grammar fixes by @scop in #1008
  • Renovate lint tools by @akx in #1017, #1028
  • Use SPDX license identifier by @vargenau in #994
  • Use aware UTC datetimes internally by @scop in #1009

New Contributors ๐Ÿ‘‘

  • @mgorny made their first contribution in #998
  • @vargenau made their first contribution in #994
  • @joeportela made their first contribution in #999
  • @encukou made their first contribution in #1027

Version 2.12.1 ๐Ÿฉน

Fixes

  • Version 2.12.0 was missing the py.typed marker file. Thanks to Alex Waygood for the fix! #975
  • The copyright year in all files was bumped to 2023. ๐Ÿ“…

Version 2.12.0 ๐Ÿ”ฅ

Deprecations & breaking changes ๐Ÿšจ

  • Python 3.6 is no longer supported (#919) - Aarni Koskela
  • The get_next_timezone_transition function is no more (#958) - Aarni Koskela
  • Locale.parse() will no longer return None; it will always return a Locale or raise an exception. Passing in None, though technically allowed by the typing, will raise. (#966)

New features ๐Ÿ’–

  • CLDR: Babel now uses CLDR 42 (#951) - Aarni Koskela ๐ŸŒ
  • Dates: pytz is now optional; Babel will prefer it but will use zoneinfo when available. (#940) - @ds-cbo
  • General: Babel now ships type annotations, thanks to Jonah Lawrence's work in multiple PRs.
  • Locales: @modifiers are now retained when parsing locales (#947) - martin f. krafft
  • Messages: JavaScript template string expression extraction is now smarter. (#939) - Johannes Wilm
  • Numbers: NaN and Infinity are now better supported (#955) - Jonah Lawrence
  • Numbers: Short compact currency formats are now supported (#926) - Jonah Lawrence
  • Numbers: There's now a Format.compact_decimal utility function. (#921) - Jonah Lawrence

Bugfixes

  • Dates: The cache for parsed datetime patterns is now bounded (#967) - Aarni Koskela
  • Messages: Fuzzy candidate matching accuracy is improved (#970) - Jean Abou Samra
  • Numbers: Compact singular formats and patterns with no numbers work correctly (#930, #932) - Jonah Lawrence, Jun Omae

Improvements & cleanup ๐Ÿงน

  • Dates: babel.dates.UTC is now an alias for datetime.timezone.utc (#957) - Aarni Koskela
  • Dates: babel.localtime was slightly cleaned up. (#952) - Aarni Koskela
  • Documentation: Documentation was improved by Maciej Olko, Jonah Lawrence, lilinjie, and Aarni Koskela.
  • Infrastructure: Babel is now being linted with pre-commit and ruff. - Aarni Koskela

Version 2.11.0 ๐ŸŒˆ

Upcoming deprecation โš ๏ธ

  • This version, Babel 2.11, is the last version of Babel to support Python 3.6. Babel 2.12 will require Python 3.7 or newer.

Improvements โœจ

  • Support for hex escapes in JavaScript string literals #877 - Przemyslaw Wegrzyn
  • Add support for formatting decimals in compact form #909 - Jonah Lawrence
  • Adapt parse_date to handle ISO dates in ASCII format #842 - Eric L.
  • Use ast instead of eval for Python string extraction #915 - Aarni Koskela
    • This also enables extraction from static f-strings. F-strings with expressions are silently ignored (but won't raise an error as they used to).

Infrastructure ๐Ÿ—๏ธ

  • Tests: Use regular asserts and pytest.raises() #875 โ€“ Aarni Koskela
  • Wheels are now built in GitHub Actions #888 โ€“ Aarni Koskela
  • Small improvements to the CLDR downloader script #894 โ€“ Aarni Koskela
  • Remove antiquated __nonzero__ methods #896 - Nikita Sobolev
  • Remove superfluous __unicode__ declarations #905 - Lukas Juhrich
  • Mark package compatible with Python 3.11 #913 - Aarni Koskela
  • Quiesce pytest warnings #916 - Aarni Koskela

Bugfixes

  • Use email.Message for pofile header parsing instead of the deprecated cgi.parse_header function. #876 โ€“ Aarni Koskela
  • Remove determining time zone via systemsetup on macOS #914 - Aarni Koskela

Documentation ๐Ÿ“š

  • Update Python versions in documentation #898 - Raphael Nestler
  • Align BSD-3 license with OSI template #912 - Lukas Kahwe Smith

Version 2.10.3 ๐Ÿฉน

This is a bugfix release for Babel 2.10.2, which was mistakenly packaged with outdated locale data. ๐Ÿ˜ญ

Thanks to Michaล‚ Gรณrny for pointing this out and Jun Omae for verifying. ๐Ÿซถ

This and future Babel PyPI packages will be built by a more automated process, which should make problems like this less likely to occur. ๐Ÿค–


Version 2.10.2 ๐Ÿฉน

This is a bugfix release for Babel 2.10.1.

  • Fallback count="other" format in format_currency() (#872) - Jun Omae
  • Fix get_period_id() with dayPeriodRule across 0:00 (#871) - Jun Omae
  • Add support for b and B period symbols in time format (#869) - Jun Omae
  • chore(docs/typo): Fixes a minor typo in a function comment (#864) - Frank Harrison

Version 2.10.1 ๐Ÿฉน

This is a bugfix release for Babel 2.10.0.

  • Messages: Fix distutils import. Regressed in #843. (#852) - Nehal J Wani
  • The wheel file is no longer marked as universal, since Babel only supports Python 3.

Version 2.10.0 ๐Ÿ‘‘

Upcoming deprecation โš ๏ธ

  • The get_next_timezone_transition() function is marked deprecated in this version and will be removed likely as soon as Babel 2.11. No replacement for this function is planned; based on discussion in #716, it's likely the function is not used in any real code. (#852) - Aarni Koskela, Paul Ganssle

Improvements โœจ

  • CLDR: Upgrade to CLDR 41.0. (#853) - Aarni Koskela ๐ŸŒ
    • The c and e plural form operands introduced in CLDR 40 are parsed, but otherwise unsupported. (#826)
    • Non-nominative forms of units are currently ignored.
  • Messages: Implement --init-missing option for pybabel update (#785) - ruro
  • Messages: For extract, you can now replace the built-in .* / _* ignored directory patterns with ones of your own. (#832) - Aarni Koskela, Kinshuk Dua
  • Messages: Add --check to verify if catalogs are up-to-date (#831) - Krzysztof Jagieล‚ล‚o
  • Messages: Add --header-comment to override default header comment (#720) - Mohamed Hafez Morsy, Aarni Koskela
  • Dates: parse_time is now giving 12-hour clock energy ๐Ÿ’… and it's way better at parsing partial times too. (#834) โ€” Aarni Koskela, David Bauer, Arthur Jovart
  • Dates: parse_date and parse_time now raise ParseError โ€” which is a subclass of ValueError btw โ€” in certain cases. (#834) โ€” Aarni Koskela
  • Dates: parse_date and parse_time now accept the format parameter. (#834) โ€” Juliette Monsel, Aarni Koskela

Infrastructure

  • The internal babel/_compat.py module is no more (#808) โ€” Hugo van Kemenade
  • Python 3.10 is officially supported โœจ (#809) โ€” Hugo van Kemenade
  • There's now a friendly GitHub issue template. (#800) โ€” รlvaro Mondรฉjar Rubio
  • Don't use the deprecated format_number function internally or in tests โ€” Aarni Koskela
  • Add GitHub URL for PyPi (#846) โ€” Andrii Oriekhov
  • Python 3.12 compatibility: prefer setuptools imports to distutils imports (#843) โ€” Aarni Koskela
  • Python 3.11 compatibility: add deprecations to l*gettext variants (#835) โ€” Aarni Koskela
  • CI: Babel is now tested with PyPy 3.7. (#851) โ€” Aarni Koskela

Bugfixes

  • Date formatting: allow using other as fallback form (#827) โ€” Aarni Koskela
  • Locales: Locale.parse() normalizes variant tags to upper case (#829) โ€” Aarni Koskela
  • A typo in the plural format for Maltese is fixed. (#796) โ€” Lukas Winkler
  • Messages: catalog date parsing is now timezone independent. (#701) โ€” rachele-collin
  • Messages: fix duplicate locations when writing without lineno (#837) โ€” Sigurd Ljรธdal
  • Messages: fix missing trailing semicolon in plural form headers (#848) โ€” farhan5900
  • CLI: fix output of --list-locales to not be a bytes repr (#845) โ€” Morgan Wahl

Documentation

  • Documentation is now correctly built again, and up to date ๐Ÿ’– (#830) โ€” Aarni Koskela

Version 2.9.1

Bugfixes

  • The internal locale-data loading functions now validate the name of the locale file to be loaded and only allow files within Babel's data directory. Thank you to Chris Lyne of Tenable, Inc. for discovering the issue! ๐Ÿซถ

Version 2.9.0

Upcoming version support changes

  • This version, Babel 2.9, is the last version of Babel to support Python 2.7, Python 3.4, and Python 3.5. ๐Ÿ˜ญ

Improvements

  • CLDR: use CLDR 37 โ€” Aarni Koskela (#734)
  • Dates: handle ZoneInfo objects in get_timezone_location, get_timezone_name โ€” Alessio Bogon (#741)
  • Numbers: add group_separator feature in number formatting โ€” Abdullah Javed Nesar (#726)

Bugfixes

  • Dates: correct default Format().timedelta format to 'long' to mute deprecation warnings โ€” Aarni Koskela
  • Import: simplify iteration code in "import_cldr.py" โ€” Felix Schwarz
  • Import: stop using deprecated ElementTree methods "getchildren()" and "getiterator()" โ€” Felix Schwarz
  • Messages: fix unicode printing error on Python 2 without TTY. โ€” Niklas Hambรผchen
  • Messages: introduce invariant that _invalid_pofile() takes unicode line. โ€” Niklas Hambรผchen
  • Tests: fix tests when using Python 3.9 โ€” Felix Schwarz
  • Tests: remove deprecated 'sudo: false' from Travis configuration โ€” Jon Dufresne
  • Tests: support Py.test 6.x โ€” Aarni Koskela
  • Utilities: LazyProxy: handle AttributeError in specified func โ€” Nikiforov Konstantin (#724)
  • Utilities: replace usage of parser.suite with ast.parse โ€” Miro Hronฤok

Documentation

  • Update parse_number comments โ€” Brad Martin (#708)
  • Add __iter__ to Catalog documentation โ€” @CyanNani123

Version 2.8.1

This is solely a patch release to make running tests on Py.test 6+ possible. Simple as that bestie ๐Ÿ’…

Bugfixes

  • Support Py.test 6 โ€” Aarni Koskela (#747, #750, #752)

Version 2.8.0

Improvements

  • CLDR: upgrade to CLDR 36.0 โ€” Aarni Koskela (#679)
  • Messages: don't even open files with the "ignore" extraction method โ€” @sebleblanc (#678)

Bugfixes

  • Numbers: fix formatting very small decimals when quantization is disabled โ€” Lev Lybin, @miluChen (#662)
  • Messages: attempt to sort all messages โ€” Mario Frasca (#651, #606)

Docs

  • Add years to changelog โ€” Romuald Brunet
  • Note that installation requires pytz โ€” Steve (Gadget) Barnes

Version 2.7.0

Possibly incompatible changes

These may be backward incompatible in some cases, as some more-or-less internal APIs have changed. Please feel free to file issues if you bump into anything strange and we'll try to help! ๐Ÿซถ

  • General: internal uses of babel.util.odict have been replaced with collections.OrderedDict from The Python standard library.

Improvements

  • CLDR: upgrade to CLDR 35.1 โ€” Alberto Mardegan, Aarni Koskela (#626, #643)
  • General: allow anchoring path patterns to the start of a string โ€” Brian Cappello (#600)
  • General: bumped version requirement on pytz โ€” @chrisbrake (#592)
  • Messages: pybabel compile: exit with code 1 if errors were encountered โ€” Aarni Koskela (#647)
  • Messages: add omit-header to update_catalog โ€” Cรฉdric Krier (#633)
  • Messages: catalog update: keep user comments from destination by default โ€” Aarni Koskela (#648)
  • Messages: skip empty message when writing mo file โ€” Cรฉdric Krier (#564)
  • Messages: small fixes to avoid crashes on badly formatted .po files โ€” Bryn Truscott (#597)
  • Numbers: parse_decimal() strict argument and suggestions โ€” Charly C (#590)
  • Numbers: don't repeat suggestions in parse_decimal strict โ€” Serban Constantin (#599)
  • Numbers: implement currency formatting with long display names โ€” Luke Plant (#585)
  • Numbers: parse_decimal(): assume spaces are equivalent to non-breaking spaces when not in strict mode โ€” Aarni Koskela (#649)
  • Performance: cache locale_identifiers() โ€” Aarni Koskela (#644)

Bugfixes

  • CLDR: skip alt=โ€ฆ for week data (minDays, firstDay, weekendStart, weekendEnd) โ€” Aarni Koskela (#634)
  • Dates: fix wrong weeknumber for 31.12.2018 โ€” BT-sschmid (#621)
  • Locale: avoid KeyError trying to get data on WindowsXP โ€” mondeja (#604)
  • Locale: get_display_name(): don't attempt to concatenate variant information to None โ€” Aarni Koskela (#645)
  • Messages: pofile: add comparison operators to _NormalizedString โ€” Aarni Koskela (#646)
  • Messages: pofile: don't crash when message.locations can't be sorted โ€” Aarni Koskela (#646)

Tooling & docs

  • Docs: remove all references to deprecated easy_install โ€” Jon Dufresne (#610)
  • Docs: switch print statement in docs to print function โ€” NotAFile
  • Docs: update all pypi.python.org URLs to pypi.org โ€” Jon Dufresne (#587)
  • Docs: use https URLs throughout project where available โ€” Jon Dufresne (#588)
  • Support: add testing and document support for Python 3.7 โ€” Jon Dufresne (#611)
  • Support: test on Python 3.8-dev โ€” Aarni Koskela (#642)
  • Support: using ABCs from collections instead of collections.abc is deprecated. โ€” Julien Palard (#609)
  • Tests: fix conftest.py compatibility with pytest 4.3 โ€” Miro Hronฤok (#635)
  • Tests: update pytest and pytest-cov โ€” Miro Hronฤok (#635)

Version 2.6.0

Possibly incompatible changes

These may be backward incompatible in some cases, as some more-or-less internal APIs have changed. Please feel free to file issues if you bump into anything strange and we'll try to help! ๐Ÿซถ

  • Numbers: refactor decimal handling code and allow bypass of decimal quantization. (@kdeldycke) (PR #538)
  • Messages: allow processing files that are in locales unknown to Babel (@akx) (PR #557)
  • General: drop support for EOL Python 2.6 and 3.3 (@hugovk) (PR #546)

Other changes

  • CLDR: use CLDR 33 (@akx) (PR #581)
  • Lists: add support for various list styles other than the default (@akx) (#552)
  • Messages: add new PoFileError exception (@Bedrock02) (PR #532)
  • Times: simplify Linux distro specific explicit timezone setting search (@scop) (PR #528)

Bugfixes

  • CLDR: avoid importing alt=narrow currency symbols (@akx) (PR #558)
  • CLDR: ignore non-Latin numbering systems (@akx) (PR #579)
  • Docs: fix improper example for date formatting (@PTrottier) (PR #574)
  • Tooling: fix some deprecation warnings (@akx) (PR #580)

Tooling & docs

  • Add explicit signatures to some date autofunctions (@xmo-odoo) (PR #554)
  • Include license file in the generated wheel package (@jdufresne) (PR #539)
  • Python 3.6 invalid escape sequence deprecation fixes (@scop) (PR #528)
  • Test and document all supported Python versions (@jdufresne) (PR #540)
  • Update copyright header years and authors file (@akx) (PR #559)

Version 2.5.3

This is a maintenance release that reverts undesired API-breaking changes that slipped into 2.5.2 (see #550).

It is based on v2.5.1 (f29eccd) with commits 7cedb84, 29da2d2 and edfb518 cherry-picked on top.


Version 2.5.2

Bugfixes

  • Revert the unnecessary PyInstaller fixes from 2.5.0 and 2.5.1 (#533) (@yagebu)

Version 2.5.1

Minor Improvements and bugfixes

  • Use a fixed datetime to avoid test failures (#520) (@narendravardi)
  • Parse multi-line __future__ imports better (#519) (@akx)
  • Fix validate_currency docstring (#522)
  • Allow normalize_locale and exists to handle various unexpected inputs (#523) (@suhojm)
  • Make PyInstaller support more robust (#525, #526) (@thijstriemstra, @akx)

Version 2.5.0

New Features

  • Numbers: add currency utilities and helpers (#491) (@kdeldycke)
  • Support PyInstaller (#500, #505) (@wodo)

Minor Improvements and bugfixes

  • Dates: add __str__ to DateTimePattern (#515) (@sfermigier)
  • Dates: fix an invalid string to bytes comparison when parsing TZ files on Py3 (#498) (@rowillia)
  • Dates: formatting zero-padded components of dates is faster (#517) (@akx)
  • Documentation: fix "Good Commits" link in CONTRIBUTING.md (#511) (@naryanacharya6)
  • Documentation: fix link to Python gettext module (#512) (@Linkid)
  • Messages: allow both dash and underscore separated locale identifiers in pofiles (#489, #490) (@akx)
  • Messages: extract Python messages in nested gettext calls (#488) (@sublee)
  • Messages: fix in-place editing of dir list while iterating (#476, #492) (@MarcDufresne)
  • Messages: stabilize sort order (#482) (@xavfernandez)
  • Time zones: honor the no-inherit marker for metazone names (#405) (@akx)

Version 2.4.0

New Features

Some of these changes might break your current code and/or tests. ๐Ÿ™ˆ

  • CLDR: CLDR 29 is now used instead of CLDR 28 (#405) (@akx)
  • Messages: new option add_location so you can format that location line however you want (#438, #459) shoutout @rrader, @alxpy ๐Ÿ’…
  • Numbers: full control over decimal behavior, finally, you get to decide (#410) tysm @etanol โœจ

Minor Improvements and bugfixes

  • Documentation: Date Fields descriptions got clearer, no more squinting (#450) @ldwoolley ๐ŸŽ€
  • Documentation: typo fixes and general docs glow-ups (#406, #412, #403, #440, #449, #463) @zyegfryed, @adamchainz, @jwilk, @akx, @roramirez, @abhishekcs10 ๐Ÿซถ
  • Messages: source encoding defaults to UTF-8 now instead of ISO-8859-1, the way it SHOULD be (#399) @asottile ๐Ÿ‘‘
  • Messages: messages get extracted in the exact order you passed them in, no chaos (#424) @ngrilly โœจ
  • Messages: JSX file extraction got way better (#392, #396, #425) @karloskar, @georgschoelly ๐Ÿ”ฅ
  • Messages: reading PO files now handles multi-line obsolete units (#429) @mbirtwell ๐Ÿ’–
  • Messages: the Python message extractor respects unicode_literals in __future__ now (#427) @sublee ๐ŸŒธ
  • Messages: Language headers roundtrip properly (#420) @kruton ๐Ÿฆ‹
  • Messages: units sitting before obsolete units are no longer wrongly marked obsolete, bestie that was so rude (#452) @mbirtwell ๐Ÿ˜ญ
  • Numbers: parse_pattern keeps the full original pattern intact now (#414) @jtwang โœจ
  • Numbers: fixed the float conversion in extract_operands (#435) @akx ๐Ÿ’…
  • Plurals: plural forms fixed for Czech and Slovak locales (#373) @ykshatroff ๐Ÿ’œ
  • Plurals: even more plural form fixes, sourced from Mozilla and CLDR references (#431) @mshenfield ๐ŸŒˆ

Internal improvements

  • local times are constructed correctly in tests now (#411) @etanol โญ
  • assorted small improvements (#437) @scop ๐ŸŽ€
  • regex flags get extracted from the regex strings themselves (#462) @singingwolfboy โœจ
  • the PO file reader is a class now and got some refactoring (#429, #452) @mbirtwell ๐Ÿ‘‘

๐ŸŒธ Version 2.3.4 ๐ŸŒธ

(Bugfix release, released on April 22th 2016)

Bugfixes

  • CLDR: the lxml library is no longer used for CLDR importing, so it shouldn't cause strange failures either. Thanks to @aronbierbaum for the bug report and @jtwang for the fix. (#393) ๐Ÿซถ
  • CLI: every last single CLI usage regression should be gone now, and both distutils and stand-alone CLIs should work like they always have. Thanks to @paxswill and @ajaeger for the bug reports. (#389) ๐Ÿ’…

๐Ÿฆ‹ Version 2.3.3 ๐Ÿฆ‹

(Bugfix release, released on April 12th 2016)

Bugfixes

  • CLI: the usage regressions that snuck in between 2.2 and 2.3 should be no more. (#386) Thanks to @ajaeger, @sebdiem and @jcristovao for bug reports and patches. ๐Ÿ™

โœจ Version 2.3.2 โœจ

(Bugfix release, released on April 9th 2016)

Bugfixes

  • Dates: period (am/pm) formatting was broken in certain locales (namely zh_TW). Thanks to @jun66j5 for the bug report. (#378, #379) ๐Ÿ˜ญ

๐Ÿ’œ Version 2.3.1 ๐Ÿ’œ

(Bugfix release because of deployment problems, released on April 8th 2016)

(yeah bestie, that's the whole thing โ€” it was a deployment situation ๐Ÿ’€)

๐Ÿ”ฅ Version 2.3 ๐Ÿ”ฅ

(Feature release, released on April 8th 2016)

Internal improvements

  • the CLI frontend and Distutils commands now share one implementation (#311) ๐ŸŽ€
  • PyPy3 is supported (#343) โœจ

Features

  • CLDR: added an API for territory language data (#315) ๐ŸŒˆ
  • Core: character order and measurement system data is imported and exposed (#368) ๐Ÿ’…
  • Dates: added an API for time interval formatting (#316) โญ
  • Dates: more pattern formats and lengths are supported (#347) ๐Ÿ’–
  • Dates: period IDs are imported and exposed (#349) ๐ŸŒธ
  • Dates: support for date-time skeleton formats has been added (#265) ๐Ÿฆ‹
  • Dates: timezone formatting has been improved (#338) ๐Ÿ‘‘
  • Messages: JavaScript extraction now supports dotted names, ES6 template strings and JSX tags (#332) ๐Ÿ”ฅ
  • Messages: npgettext is recognized by default (#341) โœจ
  • Messages: the CLI learned to accept multiple domains (#335) ๐Ÿ’…
  • Messages: the extraction commands now accept filenames in addition to directories (#324) ๐ŸŽ€
  • Units: a whole new API for unit formatting is implemented (#369) ๐ŸŒˆ

Bugfixes

  • Core: mixed-case locale IDs work more reliably (#361) ๐Ÿ’œ
  • Dates: Sโ€ฆS formats work correctly now (#360) โœจ
  • Messages: all messages are sorted correctly now if sorting has been specified (#300) ๐Ÿซถ
  • Messages: fixed the unexpected behavior caused by catalog header updating (e0e7ef1) (#320) ๐Ÿ˜ญ
  • Messages: Gettext operands are generated correctly now (#295) ๐Ÿ’…
  • Messages: message extraction has been taught to detect encodings better (#274) ๐ŸŒธ

๐ŸŒˆ Version 2.2 ๐ŸŒˆ

(Feature release, released on January 2nd 2016)

Bugfixes

  • General: add __hash__ to Locale. (#303) (2aa8074) โœจ
  • General: allow files with BOM if they're UTF-8 (#189) (da87edd) ๐ŸŽ€
  • General: localedata directory is now locale-data (#109) (2d1882e) ๐Ÿ’…
  • General: odict: fix pop method (0a9e97e) ๐Ÿ”ง
  • General: removed uses of datetime.date class from .dat files (#174) (94f6830) ๐Ÿ’œ
  • Messages: fix plural selection for Chinese (531f666) ๐ŸŒธ
  • Messages: fix typo and add semicolon in plural_forms (5784501) โœจ
  • Messages: flatten NullTranslations.files into a list (ad11101) ๐Ÿฆ‹
  • Times: FixedOffsetTimezone: fix display of negative offsets (d816803) ๐Ÿ˜ญ

Features

  • CLDR: update to CLDR 28 (#292) (9f7f4d0) ๐ŸŒˆ
  • General: add __copy__ and __deepcopy__ to LazyProxy. (a1cc3f1) ๐Ÿ‘‘
  • General: add official support for Python 3.4 and 3.5 ๐Ÿ”ฅ
  • General: improve odict performance by making key search O(1) (6822b7f) โšก
  • Locale: add an ordinal_form property to Locale (#270) (b3f3430) ๐Ÿ’…
  • Locale: add support for list formatting (37ce4fa, be6e23d) ๐ŸŽ€
  • Locale: check inheritance exceptions first (3ef0d6d) โœจ
  • Messages: allow file locations without line numbers (#279) (79bc781) ๐ŸŒธ
  • Messages: allow passing a callable to extract() (#289) (3f58516) ๐Ÿ’œ
  • Messages: support 'Language' header field of PO files (#76) (3ce842b) ๐Ÿฆ‹
  • Messages: update catalog headers from templates (e0e7ef1) โญ
  • Numbers: properly load and expose currency format types (#201) (df676ab) ๐Ÿ’–
  • Numbers: use cdecimal by default when available (b6169be) โœจ
  • Numbers: use the CLDR's suggested number of decimals for format_currency (#139) (201ed50) ๐Ÿ‘‘
  • Times: add format_timedelta(format='narrow') support (edc5eb5) ๐ŸŒˆ

๐Ÿ’– Version 2.1 ๐Ÿ’–

(Bugfix/minor feature release, released on September 25th 2015)

  • parse and honour the locale inheritance exceptions (#97) โœจ
  • fix Locale.parse using global.dat incompatible types (#174) ๐Ÿ’…
  • fix display of negative offsets in FixedOffsetTimezone (#214) ๐Ÿ˜ญ
  • improved odict performance which is used during localization file build, should improve compilation time for large projects โšก
  • add support for "narrow" format for format_timedelta ๐ŸŽ€
  • add universal wheel support ๐Ÿฆ‹
  • support 'Language' header field in .PO files (fixes #76) ๐ŸŒธ
  • test suite enhancements (coverage, broken tests fixed, etc) ๐Ÿซถ
  • documentation updated ๐Ÿ‘‘

โญ Version 2.0 โญ

(Released on July 27th 2015, codename Second Coming)

  • added support for looking up currencies that belong to a territory through the babel.numbers.get_territory_currencies() function. ๐Ÿ’ธ
  • improved Python 3 support. ๐Ÿ
  • fixed some broken tests for timezone behavior. ๐Ÿ•
  • improved various smaller things for dealing with dates. ๐Ÿ“…

๐ŸŒธ Version 1.4 ๐ŸŒธ

(bugfix release, release date to be decided)

  • fixed a bug that caused deprecated territory codes not being converted properly by the subtag resolving. This for instance showed up when trying to use und_UK as a language code which now properly resolves to en_GB. ๐Ÿ—บ๏ธ
  • fixed a bug that made it impossible to import the CLDR data from scratch on windows systems. ๐ŸชŸ

๐Ÿฆ‹ Version 1.3 ๐Ÿฆ‹

(bugfix release, released on July 29th 2013)

  • fixed a bug in likely-subtag resolving for some common locales. This primarily makes zh_CN work again which was broken due to how it was defined in the likely subtags combined with our broken resolving. This fixes #37. ๐Ÿ˜ญ
  • fixed a bug that caused pybabel to break when writing to stdout on Python 3. ๐Ÿ
  • removed a stray print that was causing issues when writing to stdout for message catalogs. ๐Ÿ’€

๐Ÿ’œ Version 1.2 ๐Ÿ’œ

(bugfix release, released on July 27th 2013)

  • included all tests in the tarball. Previously the include skipped past recursive folders. ๐Ÿ“ฆ
  • changed how tests are invoked and added separate standalone test command. This simplifies testing of the package for linux distributors. ๐Ÿง

โœจ Version 1.1 โœจ

(bugfix release, released on July 27th 2013)

  • added dummy version requirements for pytz so that it installs on pip 1.4. ๐Ÿ“Œ
  • included tests in the tarball. ๐Ÿ“ฆ

๐Ÿ‘‘ Version 1.0 ๐Ÿ‘‘

(Released on July 26th 2013, codename Revival)

  • support python 2.6, 2.7, 3.3+ and pypy - drop all other versions ๐Ÿ
  • use tox for testing on different pythons ๐Ÿงช
  • added support for the locale plural rules defined by the CLDR. ๐ŸŒ
  • added format_timedelta function to support localized formatting of relative times with strings such as "2 days" or "1 month" (ticket #126). โณ
  • fixed negative offset handling of Catalog._set_mime_headers (ticket #165). ๐Ÿ˜ญ
  • fixed the case where messages containing square brackets would break with an unpack error. ๐Ÿ’…
  • updated to CLDR 23 ๐ŸŒˆ
  • make the CLDR import script work with Python 2.7. ๐Ÿ
  • fix various typos. โœ๏ธ
  • sort output of list-locales. ๐Ÿ“‹
  • make the POT-Creation-Date of the catalog being updated equal to POT-Creation-Date of the template used to update (ticket #148). ๐Ÿ—“๏ธ
  • use a more explicit error message if no option or argument (command) is passed to pybabel (ticket #81). ๐Ÿ“ข
  • keep the PO-Revision-Date if it is not the default value (ticket #148). ๐Ÿ’พ
  • make โ€“no-wrap work by reworking โ€“width's default and mimic xgettext's behaviour of always wrapping comments (ticket #145). ๐ŸŽ€
  • add โ€“project and โ€“version options for commandline (ticket #173). ๐Ÿ’ป
  • add a __ne__() method to the Local class. ๐Ÿ”
  • explicitly sort instead of using sorted() and don't assume ordering (Jython compatibility). ๐Ÿ
  • removed ValueError raising for string formatting message checkers if the string does not contain any string formatting (ticket #150). โœจ
  • fix Serbian plural forms (ticket #213). ๐Ÿ‡ท๐Ÿ‡ธ
  • small speed improvement in format_date() (ticket #216). โšก
  • fix so frontend.CommandLineInterface.run does not accumulate logging handlers (ticket #227, reported with initial patch by dfraser) ๐Ÿชต
  • fix exception if environment contains an invalid locale setting (ticket #200) ๐Ÿ˜ญ
  • use cPickle instead of pickle for better performance (ticket #225). โšก
  • only use bankers round algorithm as a tie breaker if there are two nearest numbers, round as usual if there is only one nearest number (ticket #267, patch by Martin) ๐Ÿ”ข
  • allow disabling cache behaviour in LazyProxy (ticket #208, initial patch from Pedro Algarvio) ๐Ÿ—ƒ๏ธ
  • okay so message extraction now gets support for context-aware methods (ticket #229, and the patch came from David Rios โ€” king behavior ๐Ÿ‘‘)
  • the init and update commands now support the --no-wrap option (ticket #289) โ€” wrapping is optional now, bestie ๐Ÿ’…
  • format_decimal() stopped acting weird with fractions when the input value is a float with more than 7 significant digits (ticket #183) ๐Ÿ“
  • format_date() got fixed when you pass a datetime parameter (ticket #282, patch from Xavier Morel โ€” thank you ๐Ÿซถ)
  • format_decimal() also got fixed for small Decimal values (ticket #214, patch from George Lund โœจ)
  • messages containing a literal '\n' are handled properly now (ticket #198) โ€” no more chaos ๐Ÿ’€
  • irregular multi-line msgstr entries โ€” the ones with no empty "" as the first line โ€” are handled gracefully (ticket #171) ๐Ÿ™ˆ
  • parse_decimal() now returns Decimal objects instead of floats โ€” yes, itโ€™s an API change, so heads up (ticket #178) โš ๏ธ
  • running setup.py without installed setuptools no longer throws warnings (ticket #262) ๐ŸŒธ
  • Locale.__eq__ got modified so Locales are only equal if all their attributes โ€” language, territory, script, variant โ€” are equal ๐Ÿ’–
  • if pkg_resources is installed but no egg-info was found, it resorts to hard-coded message extractors/checkers (ticket #230) ๐Ÿ”ฅ
  • format_time() and format_datetime() now accept floats too (ticket #242) ๐ŸŽ€
  • added babel.support.NullTranslations, a class similar to gettext.NullTranslations but with all of Babelโ€™s new gettext methods (ticket #277) ๐Ÿ‘‘
  • init and update commands now support the --width option (ticket #284) ๐Ÿ’…
  • the input_dirs option for setuptools integration got fixed (ticket #232, initial patch by ร‰tienne Bersac) ๐Ÿซถ
  • the .mo file header now contains the same information as the source .po file (ticket #199) โ€” consistency ate ๐Ÿ’–
  • added support for get_language_name() on the locale objects โœจ
  • added support for get_territory_name() on the locale objects ๐ŸŽ€
  • added support for get_script_name() on the locale objects ๐ŸŒธ
  • added pluralization support for currency names, plus a ยคยคยค pattern for currencies that includes the full name ๐Ÿ’ฐ
  • Babel now depends on pytz and wraps it nicer โ€” this gives improved support for things like timezone transitions and an overall nicer API ๐ŸŒˆ
  • added support for explicit charset to PO file reading ๐Ÿ“–
  • added experimental Python 3 support ๐Ÿงช
  • added better support for returning timezone names ๐Ÿ•ฐ๏ธ
  • a Catalogโ€™s obsolete messages are no longer thrown away when updating it ๐Ÿ—‘๏ธ
  • added basic likelySubtag resolving when doing locale parsing and no match can be found ๐Ÿ”

โœจ Version 0.9.6 โœจ

(released on March 17th 2011)

  • backported r493-494: documentation typo fixes ๐Ÿ“
  • made the CLDR import script work with Python 2.7 ๐Ÿ
  • fixed various typos โœ๏ธ
  • fixed Python 2.3 compatibility (ticket #146, ticket #233) ๐Ÿ› ๏ธ
  • sorted the output of list-locales ๐Ÿ“‹
  • made the POT-Creation-Date of the catalog being updated equal to the POT-Creation-Date of the template used to update it (ticket #148) ๐Ÿ“…
  • now uses a more explicit error message if no option or argument (command) is passed to pybabel (ticket #81) ๐Ÿšจ
  • keeps the PO-Revision-Date if it is not the default value (ticket #148) ๐Ÿ’พ
  • made --no-wrap work by reworking --widthโ€™s default and mimicking xgettextโ€™s behaviour of always wrapping comments (ticket #145) ๐Ÿ’…
  • fixed negative offset handling of Catalog._set_mime_headers (ticket #165) ๐Ÿ”ง
  • added --project and --version options for commandline (ticket #173) ๐ŸŽ›๏ธ
  • added a __ne__() method to the Local class ๐Ÿง
  • explicitly sorts instead of using sorted() and doesnโ€™t assume ordering (Python 2.3 and Jython compatibility) ๐Ÿ”€
  • removed ValueError raising for string formatting message checkers if the string does not contain any string formatting (ticket #150) ๐Ÿšซ
  • fixed Serbian plural forms (ticket #213) ๐Ÿ‡ท๐Ÿ‡ธ
  • small speed improvement in format_date() (ticket #216) โšก
  • fixed number formatting for locales where CLDR specifies alt or draft items (ticket #217) ๐Ÿ”ข
  • fixed bad check in format_time (ticket #257, reported with patch and tests by jomae) ๐Ÿ•ต๏ธ
  • fixed so frontend.CommandLineInterface.run does not accumulate logging handlers (ticket #227, reported with initial patch by dfraser) ๐Ÿชต
  • fixed exception if environment contains an invalid locale setting (ticket #200) ๐ŸŒ

โœจ Version 0.9.5 โœจ

(released on April 6th 2010)

  • fixed the case where messages containing square brackets would break with an unpack error ๐Ÿ™…
  • backport of r467: fuzzy matching regarding plurals should NOT be checked against len(message.id) because this is always 2 โ€” instead, it should be checked against catalog.num_plurals (ticket #212) ๐Ÿง 

โœจ Version 0.9.4 โœจ

(released on August 25th 2008)

  • currency symbol definitions that are defined with choice patterns in the CLDR data are no longer imported, so the symbol code will be used instead ๐Ÿ’ฑ
  • fixed quarter support in date formatting ๐Ÿ“†
  • fixed a serious memory leak that was introduced by the support for CLDR aliases in 0.9.3 (ticket #128) ๐Ÿšฐ
  • locale modifiers such as @euro are now stripped from locale identifiers when parsing (ticket #136) ๐Ÿ’ถ
  • the system locales C and POSIX are now treated as aliases for en_US_POSIX, for which the CLDR provides the appropriate data โ€” thanks to Manlio Perillo for the suggestion ๐Ÿซถ
  • fixed JavaScript extraction for regular expression literals (ticket #138) and concatenated strings ๐ŸŸจ
  • the Translation class in babel.support can now manage catalogs with different message domains, and exposes the family of d*gettext functions (ticket #137) ๐ŸŒ

โœจ Version 0.9.3 โœจ

(released on July 9th 2008)

  • fixed invalid message extraction methods causing an UnboundLocalError ๐Ÿ›‘
  • extraction method specification can now use a dot instead of the colon to separate module and function name (ticket #105) ๐Ÿ”น
  • fixed message catalog compilation for locales with more than two plural forms (ticket #95) ๐Ÿงฎ
  • fixed compilation of message catalogs for locales with more than two plural forms where the translations were empty (ticket #97) ๐Ÿ•ณ๏ธ
  • the stripping of the comment tags in comments is optional now and is done for each line in a comment ๐Ÿ“
  • added a JavaScript message extractor ๐ŸŸจ
  • updated to CLDR 1.6 ๐ŸŒ
  • fixed timezone calculations when formatting datetime and time values ๐Ÿ•ฐ๏ธ
  • added a get_plural function into the plurals module that returns the correct plural forms for a locale as a tuple ๐Ÿ”ข
  • added support for alias definitions in the CLDR data files, meaning that the chance for items missing in certain locales should be greatly reduced (ticket #68) ๐Ÿงฉ

โœจ Version 0.9.2 โœจ

(released on February 4th 2008)

  • fixed catalogsโ€™ charset values not being recognized (ticket #66) ๐Ÿ”ค
  • numerous improvements to the default plural forms ๐Ÿง 
  • fixed fuzzy matching when updating message catalogs (ticket #82) ๐ŸŽฏ
  • fixed bug in catalog updating that in some cases pulled in translations from different catalogs based on the same template ๐Ÿ›
  • location lines in PO files no longer get wrapped at hyphens in file names (ticket #79) ๐Ÿ“„
  • fixed division by zero error in catalog compilation on empty catalogs (ticket #60) โž—

โœจ Version 0.9.1 โœจ

(released on September 7th 2007)

  • fixed catalog updating when a message is merged that was previously simple but now has a plural form, for example by moving from gettext to ngettext, or vice versa ๐Ÿ”„
  • fixed time formatting for 12 am and 12 pm ๐Ÿ•›
  • fixed output encoding of the pybabel --list-locales command ๐ŸŒˆ
  • MO files are now written in binary mode on Windows (ticket #61) ๐ŸชŸ

โœจ Version 0.9 โœจ

(released on August 20th 2007)

  • the new_catalog distutils command has been renamed to init_catalog for consistency with the command-line frontend ๐Ÿ”
  • added compilation of message catalogs to MO files (ticket #21) ๐Ÿ—๏ธ
  • added updating of message catalogs from POT files (ticket #22) ๐Ÿ”„
  • support for significant digits in number formatting ๐Ÿ”ข
  • apply proper โ€œbankerโ€™s roundingโ€ in number formatting in a cross-platform manner ๐Ÿฆ
  • the number formatting functions now also work with numbers represented by Python Decimal objects (ticket #53) ๐Ÿ
  • added extensible infrastructure for validating translation catalogs โœ…
  • fixed the extractor not filtering out messages that didnโ€™t validate against the keywordโ€™s specification (ticket #39) ๐Ÿงน
  • fixed the extractor raising an exception when encountering an empty string msgid โ€” it now emits a warning to stderr ๐Ÿšจ
  • numerous Python message extractor fixes: it now handles nested function calls within a gettext function call correctly, uses the correct line number for multi-line function calls, and other small fixes (tickets ticket #38 and ticket #39) ๐Ÿ
  • improved support for detecting Python string formatting fields in message strings (ticket #57) ๐Ÿ”
  • CLDR upgraded to the 1.5 release ๐ŸŒ
  • improved timezone formatting ๐Ÿ•ฐ๏ธ
  • implemented scientific number formatting ๐Ÿงช
  • added mechanism to lookup locales by alias, for cases where browsers insist on including only the language code in the Accept-Language header, and sometimes even the incorrect language code ๐Ÿ™„

โœจ Version 0.8.1 โœจ

(released on July 2nd 2007)

  • default_locale() would fail when the value of the LANGUAGE environment variable contained multiple language codes separated by colon, as is explicitly allowed by the GNU gettext tools. As the default_locale() function is called at the module level in some modules, this bug would completely break importing these modules on systems where LANGUAGE is set that way ๐Ÿ’ฅ
  • the character set specified in PO template files is now respected when creating new catalog files based on that template โ€” this allows the use of characters outside the ASCII range in POT files (ticket #17) ๐ŸŒ
  • the default ordering of messages in generated POT files, which is based on the order those messages are found when walking the source tree, is no longer subject to differences between platforms; directory and file names are now always sorted alphabetically ๐Ÿ“‚
  • the Python message extractor now respects the special encoding comment to be able to handle files containing non-ASCII characters (ticket #23) ๐Ÿ
  • added N_ (gettext noop) to the extractorโ€™s default keywords ๐Ÿท๏ธ
  • made locale string parsing more robust, and also take the script part into account (ticket #27) ๐Ÿงฉ
  • added a function to list all locales for which locale data is available ๐Ÿ“‹
  • added a command-line option to the pybabel command which prints out all available locales (ticket #24) ๐Ÿ’ป
  • the name of the command-line script has been changed from just babel to pybabel to avoid a conflict with the OpenBabel project (ticket #34) ๐Ÿšซ

โœจ Version 0.8 โœจ

(released on June 20th 2007)

  • first public release ๐ŸŽ‰

License ๐Ÿ“œ

Babel is licensed under a three clause BSD License. Translated for the group chat: do whatever you want with it, bestie, as long as โ€”

  • the copyright in Babel sticks around ๐Ÿซถ
  • the conditions are not modified
  • the disclaimer is present

And one more: you must not use the names of the authors to promote derivatives of the software without written consent. Like, ask first, okay? Consent is the whole vibe.

The full license text lives down below (Babel License). No skipping, iykyk.


Authors ๐Ÿ‘‘

Babel is written and maintained by the Babel team and various contributors. Roll call โ€” this is a long one, so hydrate before you scroll, babe:

  • Aarni Koskela
  • Christopher Lenz
  • Armin Ronacher
  • Alex Morega
  • Lasse Schuirmann
  • Felix Schwarz
  • Pedro Algarvio
  • Jeroen Ruigrok van der Werven
  • Philip Jenvey
  • benselme
  • Isaac Jurado
  • Tomas R.
  • Tobias Bieniek
  • Erick Wilder
  • Jonah Lawrence
  • Michael Birtwell
  • Jonas Borgstrรถm
  • Kevin Deldycke
  • Ville Skyttรค
  • Jon Dufresne
  • Hugo van Kemenade
  • Jun Omae
  • Heungsub Lee
  • Boris Verkhovskiy
  • Jakob Schnitzer
  • Sachin Paliwal
  • Alex Willmer
  • Daniel Neuhauser
  • Miro Hronฤok
  • Cรฉdric Krier
  • Luke Plant
  • Jennifer Wang
  • Lukas Balaga
  • sudheesh001
  • Jean Abou Samra
  • Niklas Hambรผchen
  • Changaco
  • Xavier Fernandez
  • KO. Mattsson
  • Sรฉbastien Diemer
  • alexbodn@gmail.com
  • saurabhiiit
  • srisankethu
  • Erik Romijn
  • Lukas B
  • Ryan J Ollos
  • Arturas Moskvinas
  • Leonardo Pistone
  • Hyunjun Kim
  • Bart Broere
  • Guillaume Gauvrit
  • clach04
  • wandrew004
  • James McKinney
  • Tomรกลก Hrnฤiar
  • Gabe Sherman
  • mattdiaz007
  • Dylan Kiss
  • Daniel Roschka
  • buhtz
  • Bohdan Malomuzh
  • Leonid
  • Ronan Amicel
  • Christian Clauss
  • Best Olunusi
  • Teo
  • Ivan Koldakov
  • Rico Hermans
  • Daniel
  • Oleh Prypin
  • Petr Viktorin
  • Jean Abou-Samra
  • Joe Portela
  • Marc-Etienne Vargenau
  • Michaล‚ Gรณrny
  • Alex Waygood
  • Maciej Olko
  • martin f. krafft
  • DS/Charlie
  • lilinjie
  • Johannes Wilm
  • Eric L
  • Przemyslaw Wegrzyn
  • Lukas Kahwe Smith
  • Lukas Juhrich
  • Nikita Sobolev
  • Raphael Nestler
  • Frank Harrison
  • Nehal J Wani
  • Mohamed Morsy
  • Krzysztof Jagieล‚ล‚o
  • Morgan Wahl
  • farhan5900
  • Sigurd Ljรธdal
  • Andrii Oriekhov
  • rachele-collin
  • Lukas Winkler
  • Juliette Monsel
  • รlvaro Mondรฉjar Rubio
  • ruro
  • Alessio Bogon
  • Nikiforov Konstantin
  • Abdullah Javed Nesar
  • Brad Martin
  • Tyler Kennedy
  • CyanNani123
  • sebleblanc
  • He Chen
  • Steve (Gadget) Barnes
  • Romuald Brunet
  • Mario Frasca
  • BT-sschmid
  • Alberto Mardegan
  • mondeja
  • NotAFile
  • Julien Palard
  • Brian Cappello
  • Serban Constantin
  • Bryn Truscott
  • Chris
  • Charly C
  • PTrottier
  • xmo-odoo
  • StevenJ
  • Jungmo Ku
  • Simeon Visser
  • Narendra Vardi
  • Stefane Fermigier
  • Narayan Acharya
  • Franรงois Magimel
  • Wolfgang Doll
  • Roy Williams
  • Marc-Andrรฉ Dufresne
  • Abhishek Tiwari
  • David Baumgold
  • Alex Kuzmenko
  • Georg Schรถlly
  • ldwoolley
  • Rodrigo Ramรญrez Norambuena
  • Jakub Wilk
  • Roman Rader
  • Max Shenfield
  • Nicolas Grilly
  • Kenny Root
  • Adam Chainz
  • Sรฉbastien Fievet
  • Anthony Sottile
  • Yuriy Shatrov
  • iamshubh22
  • Sven Anderson
  • Eoin Nugent
  • Roman Imankulov
  • David Stanek
  • Roy Wellington โ…ฃ
  • Florian Schulze
  • Todd M. Guerra
  • Joseph Breihan
  • Craig Loftus
  • The Gitter Badger
  • Rรฉgis Behmo
  • Julen Ruiz Aizpuru
  • astaric
  • Felix Yan
  • Philip_Tzou
  • Jesรบs Espino
  • Jeremy Weinstein
  • James Page
  • masklinn
  • Sjoerd Langkemper
  • Matt Iversen
  • Alexander A. Dyshev
  • Dirkjan Ochtman
  • Nick Retallack
  • Thomas Waldmann
  • xen

Every single one of them ate. ๐Ÿ’…


Babel was previously developed under the Copyright of Edgewall Software. The following copyright notice holds true for releases before 2013:

โ€œCopyright (c) 2007 - 2011 by Edgewall Softwareโ€

In addition to the regular contributions, Babel includes a fork of Lennart Regebroโ€™s tzlocal that originally was licensed under the CC0 license. The original copyright of that project is:

โ€œCopyright 2013 by Lennart Regebroโ€

General License Definitions ๐ŸŒธ

This bit has the full license texts for Babel and the documentation. Here's the key so nothing gets confusing:

  • โ€œAUTHORSโ€ hereby refers to all the authors listed in the Authors section. (Yes, that whole list up there. All of them. ๐Ÿ‘‘)
  • The โ€œBabel Licenseโ€ applies to all the source code shipped as part of Babel (Babel itself as well as the examples and the unit tests) as well as documentation.
  • The โ€œUnicode Licenseโ€ applies to the transformed Unicode Common Locale Data Repository (CLDR) data files shipped with Babel, in the directory babel/locale-data.

Babel License โญ

Okay so this next part is legal text and I am not paraphrasing it, because that would be a crime. Verbatim, untouched, exactly as it ships:

Copyright (c) 2013-2026 by the Babel Team, see AUTHORS for more
information.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the
      distribution.

   3. Neither the name of the copyright holder nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS โ€œAS
ISโ€ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Unicode License ๐ŸŒˆ

And this one too โ€” transformed CLDR data files, same deal, verbatim only:

UNICODE LICENSE V3

COPYRIGHT AND PERMISSION NOTICE

Copyright ยฉ 2004-2025 Unicode, Inc.

NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.

Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the โ€œData Filesโ€)
or software and any associated documentation (the โ€œSoftwareโ€) to deal in
the Data Files or Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute,
and/or sell copies of the Data Files or Software, and to permit persons
to whom the Data Files or Software are furnished to do so, provided that
either (a) this copyright and permission notice appear with all copies
of the Data Files or Software, or (b) this copyright and permission
notice appear in associated Documentation.

THE DATA FILES AND SOFTWARE ARE PROVIDED โ€œAS ISโ€, WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT OF THIRD PARTY RIGHTS.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THE DATA FILES OR SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or
other dealings in these Data Files or Software without prior written
authorization of the copyright holder.

SPDX-License-Identifier: Unicode-3.0

AUTHOR ๐Ÿ’–

The Babel Team


2025, The Babel Team


and that's the whole page, bestie!! ๐Ÿฆ‹ This was BABEL(1), version 2.17, dated Feb 25, 2026 โ€” thanks for reading all the way to the end, you're literally the main character for that. go touch some grass, drink water, and come back when you need to localize something ๐Ÿ’…โœจ

๐Ÿ“ฆ where this page comes from