BABEL(1)
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, andfull.
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, ifyis used for the year,yymight produce "99", whereasyyyyproduces "1999". For most numerical fields, the number of characters specifies the field width. For example, ifhis the hour,hmight produce "5", buthhproduces "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 asybut 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 aftery. Recommend use with thewSymbol.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, or5for the narrow name.
- Week ๐
wโ Week of year according to the ISO year-week calendar. This may have52or53weeks depending on the year. Recommend use with theYsymbol.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 asEexcept 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:AMorPM.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 asz, 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โ Digit1-9โ โ1โ through โ9โ indicate rounding.@โ Significant digit#โ Digit, zero shows as absent.โ Decimal separator or monetary decimal separator-โ Minus sign,โ Grouping separatorEโ 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 version2.8.0, theparse_numberfunction 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
msgfmtto 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--keywordcommand-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-commentscommand-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 intosetup.pyscripts, you can also provide this configuration a different way โ namely as a keyword argument to thesetup()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:
- a document or help manual, or
- 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
dngettextfunction typically expects translatable strings as its second and third arguments, so you'd passdngettext: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
foothat acts likegettext(the argument is a message) or likepgettext(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 themsgid โโentry in the header--output-file(-o) โ name of the output file--width(-w) โ set the output line width (default76)--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 (defaultFalse)--sort-by-fileโ sort output by file location (defaultFalse)--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:
LANGUAGELC_ALLLC_CTYPELANG
Parameters
categoryโ one of theLC_XXXenvironment variable namesaliasesโ 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 useravailableโ the list of locale identifiers availablealiasesโ a dictionary of aliases for locale identifierssepโ 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_TWby itself is not a locale that exists but Babel can automatically expand it to the full form ofzh_hant_TW. Note that this expansion is only taking place if no locale exists otherwise. For instance there is a localeenthat can exist by itself.
Raises</p>
ValueErrorโ if the string does not appear to be a valid locale identifierUnknownLocaleErrorโ if no locale data is available for the requested localeTypeErrorโ if the identifier is not a string or aLocaleValueErrorโ 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: ๐
LANGUAGELC_ALLLC_CTYPELANG
Parameters</p>
- category โ one or more of the
LC_XXXenvironment 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 useravailableโ the list of locale strings availablesepโ character that separates the different parts of the locale stringsaliasesโ 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_currenciescurrency_fractionslanguage_aliaseslikely_subtagsparent_exceptionsscript_aliasesterritory_aliasesterritory_currenciesterritory_languagesterritory_zonesvariant_aliaseswindows_zone_mappingzone_aliaseszone_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 stringsepโ 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 byparse_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; ifNone, the current date and time is usedformatโ one of"full","long","medium", or"short", or a custom date/time patterntzinfoโ the timezone to apply to the time for displaylocaleโ aLocaleobject 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; ifNone, the current date is usedformatโ one of"full","long","medium", or"short", or a custom date/time patternlocaleโ aLocaleobject 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; ifNone, the current time in UTC is used โฐformatโ one of"full","long","medium", or"short", or a custom date/time patterntzinfoโ the time-zone to apply to the time for display ๐localeโ aLocaleobject 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โ atimedeltaobject representing the time difference to format, or the delta in seconds as an int valuegranularityโ 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 unitadd_directionโ if this flag is set toTruethe 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โ aLocaleobject 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; ifNone, the current time in UTC is usedtzinfoโ 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, aKeyErroris thrown. ๐localeโ aLocaleobject 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; ifNone, the current date and time in UTC is usedwidthโ either"long"or"short"or"iso8601"or"iso8601_short"localeโ theLocaleobject, or a locale string. Defaults to the system time locale.return_zโTrueorFalse; 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โ thedatetimeortzinfoobject that decides which timezone we're talking about; if it'sNone, the current date and time in UTC is assumedlocaleโ theLocaleobject, or just a locale string. Defaults to the system time locale.return_cityโTrueorFalse, and if it'sTruethen 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โ thedatetimeortzinfoobject that determines the timezone; if atzinfoobject is used, the resulting display name will be generic, i.e. independent of daylight savings time; ifNone, the current date in UTC is assumedwidthโ 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 thedatetimeobject passed in. If nodatetimeobject is passed in, the'generic'variation is assumed. The following values are valid:'generic','daylight'and'standard'.localeโ theLocaleobject, or a locale string. Defaults to the system time locale.return_zoneโTrueorFalse. 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โ theLocaleobject, 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โ theLocaleobject, 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โ theLocaleobject, 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โ theLocaleobject, 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โ theLocaleobject, 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โ theLocaleobject, 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โ theLocaleobject, 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 datelocaleโ a Locale object or a locale identifierlocaleโ 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"(peepget_time_formatfor 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 timelocaleโ 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, seeget_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 coderegional(bool) โ Whether to return regionally official languages toode_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 liststyleโ 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 messagesstringโ the translated message string, or a(singular, plural)tuple for pluralizable messageslocationsโ a sequence of(filename, lineno)tuples, so basically the receipts of where each message was spotted ๐flagsโ a set or sequence of flagsauto_commentsโ a sequence of automatic commentsuser_commentsโ a sequence of user commentsprevious_idโ the previous message ID, or a(singular, plural)tuple for pluralizable messageslinenoโ the line number where themsgidline was found in the PO file, if anycontextโ 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 IDcontextโ the message context, orNonefor 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 IDcontextโ the message context, orNonefor 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 fileno_fuzzy_matchingโ whether to use fuzzy matching of message IDsupdate_header_commentโ whether to copy the header comment from the templatekeep_user_commentsโ whether to keep user comments from the old catalogupdate_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 patternsoptions_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 stringscomment_tagsโ a list of tags of translator comments to search for and include in the resultscallbackโ 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 orderstrip_comment_tagsโ a flag that if set toTruecauses 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 returnTrueif 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 frommethodโ 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 stringscomment_tagsโ a list of translator tags to search for and include in the resultsstrip_comment_tagsโ a flag that if set toTruecauses 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 formpackage.module:funcnameorpackage.module.funcname), the corresponding function will be imported and usedfileobjโ the file-like object the messages should be extracted fromkeywordsโ 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 stringscomment_tagsโ a list of translator tags to search for and include in the resultsoptionsโ a dictionary of additional options (optional)strip_comment_tagsโ a flag that if set toTruecauses 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 fromkeywordsโ a list of keywords (i.e. function names) that should be recognized as translation functionscomment_tagsโ a list of translator tags to search for and include in the resultsoptionsโ 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 fromkeywordsโ a list of keywords (i.e. function names) that should be recognized as translation functionscomment_tagsโ a list of translator tags to search for and include in the resultsoptionsโ a dictionary of additional options (optional) Supported options are: *jsxโ set to false to disable JSX/E4X support. *template_stringโ if True, supportsgettext(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 theGNUTranslations._parsemethod of thegettextmodule 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 tocatalogโ theCataloginstanceuse_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 fromlocaleโ the locale identifier orLocaleobject, orNoneif the catalog is not bound to a locale (which basically means itโs a template)domainโ the message domainignore_obsoleteโ whether to ignore obsolete messages in the inputcharsetโ 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 tocatalogโ theCataloginstancewidthโ the maximum line width for the generated output; useNone,0, or a negative number to completely disable line wrappingno_locationโ do not emit a location comment for every messageomit_headerโ do not include themsgid ""entry at the top of the outputsort_outputโ whether to sort the messages in the output bymsgidsort_by_fileโ whether to sort the messages in the output by their locationsignore_obsoleteโ whether to ignore obsolete messages and not include them in the output; by default they are included as commentsinclude_previousโ include the oldmsgidas a comment when updating the cataloginclude_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 formatlocaleโ theLocaleobject 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 formatformatlocaleโ theLocaleobject or locale identifier. Defaults to the system numeric locale.decimal_quantizationโ Truncate and round high-precision numbers to the format pattern. Defaults toTrue.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 formatformat_typeโ Compact format to use ("short"or"long")localeโ theLocaleobject or locale identifier. Defaults to the system numeric locale.fraction_digitsโ Number of digits after the decimal point to use. Defaults to0.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 formatcurrencyโ the currency codeformatโ the format string to uselocaleโ theLocaleobject or locale identifier. Defaults to the system currency locale or numeric locale.currency_digitsโ use the currency's natural number of decimal digitsformat_typeโ the currency format type to usedecimal_quantizationโ Truncate and round high-precision numbers to the format pattern. Defaults toTrue.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 formatcurrencyโ the currency codeformat_typeโ the compact format type to use. Defaults to"short".localeโ theLocaleobject or locale identifier. Defaults to the system currency locale or numeric locale.fraction_digitsโ Number of digits after the decimal point to use. Defaults to0.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 formatformatlocaleโ theLocaleobject or locale identifier. Defaults to the system numeric locale.decimal_quantizationโ Truncate and round high-precision numbers to the format pattern. Defaults toTrue.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 formatformatlocaleโ theLocaleobject or locale identifier. Defaults to the system numeric locale.decimal_quantizationโ Truncate and round high-precision numbers to the format pattern. Defaults toTrue.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 parselocaleโ theLocaleobject 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 numberUnsupportedNumberingSystemErrorโ 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 parselocaleโ theLocaleobject or locale identifier. Defaults to the system numeric locale.strictโ controls whether numbers formatted in a weird way are accepted or rejectednumbering_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 numberUnsupportedNumberingSystemErrorโ 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โ theLocaleobject 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โ theLocaleobject 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โ theLocaleobject 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โ theLocaleobject 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โ theLocaleobject 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โ theLocaleobject 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 toTrue, 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 aPluralRuleobject.
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 aPluralRuleobject.
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 aPluralRuleobject.
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 aPluralRuleobject.
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
Translationsinstance 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
MOfiles - locales โ the list of locales in order of preference (items in this list can be either
Localeobjects 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
Translationsinstance 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
Localeobject 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. Seeformat_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. Seeformat_unit.lengthโ the formatting length."short","long"or"narrow".formatโ an optional format, exactly as accepted byformat_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
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.xmllengthโ"short","long"or"narrow".localeโ the Locale object or locale identifier. Defaults to the system numeric locale.
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-8should 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_territoriesby @akx in #1220 - Messages: Improve extract performance via ignoring directories early during
os.walkby @akx in #968 - Messages: Merge in per-format keywords and
auto_commentsby @akx in #1243 - Messages: Update keywords for extraction of
dpgettextanddnpgettextby @mardiros in #1235 - Messages: Validate all plurals in Python format checker by @tomasr8 in #1188
- Time: Use standard library timezone instead of
FixedOffsetTimezoneby @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
KeyErrorthat 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_poby @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.mdby @akx in #1229 - Docs: Remove
ustring prefix from docs by @verhovsky in #1174 - Docs: Update
dates.rstwith 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.tomlby @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_timeby @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_timedeltawhen usingadd_direction, by @akx in #1163 - Messages:
.pofiles 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_MONETARYis now preferred when formatting currencies, by @akx in #1173
Bugfixes
- Dates: Make seconds optional in
parse_timetime formats by @tomasr8 in #1141 - Dates: Replace
str.indexwithstr.findby @tomasr8 in #1130 - Dates: Strip extra leading slashes in
/etc/localtimeby @akx in #1165 - Dates: Week numbering and formatting of dates with week numbers was repaired by @jun66j5 in #1179
- General: Improve handling for
locale=Noneby @akx in #1164 - General: Remove redundant assignment in
Catalog.__setitem__by @tomasr8 in #1167 - Messages: Fix extracted
linenowith 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_mologic regardingcatalog.charsetby @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.pyto add compatibility with sphinx 8 by @hrnciar in #1155 - General: Import
Literalfrom thetypingmodule by @tomasr8 in #1175 - General: Replace
OrderedDictwith justdictby @tomasr8 in #1149 - Messages: Mark
wraptextdeprecated; useTextWrapperdirectly inwrite_poby @akx in #1140
Infrastructure
- Add
tzdataas dev dependency and sync withtox.iniby @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.metadatafor finding entrypoints by @akx in #1102 - Dates: Avoid crashing on importing
localtimewhenTZis malformed by @akx in #1100 ๐ญ (the malformed-TZjump scare is no more) - Messages: Allow parsing
.pofiles that have an extant but emptyLanguageheader by @akx in #1101 - Messages: Fix
--ignore-dirsbeing incorrectly read (#1094) by @john-psina and @Edwin18 in #1052 and #1095 - Messages: Make
pgettextsearch plurals when translation is not found by @tomasr8 in #1085
Infrastructure ๐๏ธ
- Replace deprecated
ast.Strwithast.Constantby @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.pyby @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
.poIO 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_symbolswill 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 nowLocale.number_symbols['latn']['decimal']. ๐- Babel no longer directly depends on either
distutilsorsetuptools; if you had been using the Babel setuptools command extensions, you would need to explicitly depend onsetuptoolsโ though given you're runningsetup.pyyou 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_translatorto be passed as an option toextract_messageby @AivGitHub in #1044 - Frontend: Decouple
pybabelCLI 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_namesby @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-Datefor 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_keylogic 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.typedmarker 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_transitionfunction is no more (#958) - Aarni Koskela Locale.parse()will no longer returnNone; it will always return aLocaleor raise an exception. Passing inNone, though technically allowed by the typing, will raise. (#966)
New features ๐
- CLDR: Babel now uses CLDR 42 (#951) - Aarni Koskela ๐
- Dates:
pytzis now optional; Babel will prefer it but will usezoneinfowhen available. (#940) - @ds-cbo - General: Babel now ships type annotations, thanks to Jonah Lawrence's work in multiple PRs.
- Locales:
@modifiersare now retained when parsing locales (#947) - martin f. krafft - Messages: JavaScript template string expression extraction is now smarter. (#939) - Johannes Wilm
- Numbers:
NaNandInfinityare now better supported (#955) - Jonah Lawrence - Numbers: Short compact currency formats are now supported (#926) - Jonah Lawrence
- Numbers: There's now a
Format.compact_decimalutility 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.UTCis now an alias fordatetime.timezone.utc(#957) - Aarni Koskela - Dates:
babel.localtimewas 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_dateto handle ISO dates in ASCII format #842 - Eric L. - Use
astinstead ofevalfor 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.Messagefor pofile header parsing instead of the deprecatedcgi.parse_headerfunction. #876 โ Aarni Koskela - Remove determining time zone via
systemsetupon 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 informat_currency()(#872) - Jun Omae - Fix
get_period_id()withdayPeriodRuleacross 0:00 (#871) - Jun Omae - Add support for
bandBperiod 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
candeplural form operands introduced in CLDR 40 are parsed, but otherwise unsupported. (#826) - Non-nominative forms of units are currently ignored.
- The
- Messages: Implement
--init-missingoption forpybabel 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
--checkto verify if catalogs are up-to-date (#831) - Krzysztof Jagieลลo - Messages: Add
--header-commentto override default header comment (#720) - Mohamed Hafez Morsy, Aarni Koskela - Dates:
parse_timeis 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_dateandparse_timenow raiseParseErrorโ which is a subclass ofValueErrorbtw โ in certain cases. (#834) โ Aarni Koskela - Dates:
parse_dateandparse_timenow accept theformatparameter. (#834) โ Juliette Monsel, Aarni Koskela
Infrastructure
- The internal
babel/_compat.pymodule 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_numberfunction internally or in tests โ Aarni Koskela - Add GitHub URL for PyPi (#846) โ Andrii Oriekhov
- Python 3.12 compatibility: prefer
setuptoolsimports todistutilsimports (#843) โ Aarni Koskela - Python 3.11 compatibility: add deprecations to
l*gettextvariants (#835) โ Aarni Koskela - CI: Babel is now tested with PyPy 3.7. (#851) โ Aarni Koskela
Bugfixes
- Date formatting: allow using
otheras 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-localesto 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
ZoneInfoobjects inget_timezone_location,get_timezone_nameโ Alessio Bogon (#741) - Numbers: add
group_separatorfeature in number formatting โ Abdullah Javed Nesar (#726)
Bugfixes
- Dates: correct default
Format().timedeltaformat 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: handleAttributeErrorin specified func โ Nikiforov Konstantin (#724) - Utilities: replace usage of
parser.suitewithast.parseโ Miro Hronฤok
Documentation
- Update
parse_numbercomments โ Brad Martin (#708) - Add
__iter__toCatalogdocumentation โ @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.odicthave been replaced withcollections.OrderedDictfrom 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 code1if errors were encountered โ Aarni Koskela (#647) - Messages: add
omit-headertoupdate_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
.pofiles โ Bryn Truscott (#597) - Numbers:
parse_decimal()strict argument and suggestions โ Charly C (#590) - Numbers: don't repeat suggestions in
parse_decimalstrict โ 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
KeyErrortrying to get data on WindowsXP โ mondeja (#604) - Locale:
get_display_name(): don't attempt to concatenate variant information toNoneโ Aarni Koskela (#645) - Messages: pofile: add comparison operators to
_NormalizedStringโ Aarni Koskela (#646) - Messages: pofile: don't crash when
message.locationscan'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.orgURLs topypi.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
collectionsinstead ofcollections.abcis deprecated. โ Julien Palard (#609) - Tests: fix
conftest.pycompatibility 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
PoFileErrorexception (@Bedrock02) (PR #532) - Times: simplify Linux distro specific explicit timezone setting search (@scop) (PR #528)
Bugfixes
- CLDR: avoid importing
alt=narrowcurrency 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_currencydocstring (#522) - Allow
normalize_localeandexiststo 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__toDateTimePattern(#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_locationso 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_literalsin__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_patternkeeps 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
lxmllibrary 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:
npgettextis 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โฆSformats 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__toLocale. (#303) (2aa8074) โจ - General: allow files with BOM if they're UTF-8 (#189) (
da87edd) ๐ - General:
localedatadirectory is nowlocale-data(#109) (2d1882e) ๐ - General:
odict: fixpopmethod (0a9e97e) ๐ง - General: removed uses of
datetime.dateclass from.datfiles (#174) (94f6830) ๐ - Messages: fix plural selection for Chinese (
531f666) ๐ธ - Messages: fix typo and add semicolon in
plural_forms(5784501) โจ - Messages: flatten
NullTranslations.filesinto a list (ad11101) ๐ฆ - Times:
FixedOffsetTimezone: fix display of negative offsets (d816803) ๐ญ
Features
- CLDR: update to CLDR 28 (#292) (
9f7f4d0) ๐ - General: add
__copy__and__deepcopy__toLazyProxy. (a1cc3f1) ๐ - General: add official support for Python 3.4 and 3.5 ๐ฅ
- General: improve
odictperformance by making key search O(1) (6822b7f) โก - Locale: add an
ordinal_formproperty toLocale(#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
cdecimalby 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.parseusingglobal.datincompatible types (#174) ๐ - fix display of negative offsets in
FixedOffsetTimezone(#214) ๐ญ - improved
odictperformance 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
.POfiles (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_UKas a language code which now properly resolves toen_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_CNwork 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
pybabelto 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
pytzso 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_timedeltafunction 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-Dateof the catalog being updated equal toPOT-Creation-Dateof 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-Dateif it is not the default value (ticket #148). ๐พ - make
โno-wrapwork by reworkingโwidth's default and mimicxgettext's behaviour of always wrapping comments (ticket #145). ๐ - add
โprojectandโversionoptions for commandline (ticket #173). ๐ป - add a
__ne__()method to theLocalclass. ๐ - explicitly sort instead of using
sorted()and don't assume ordering (Jython compatibility). ๐ - removed
ValueErrorraising 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.rundoes not accumulate logging handlers (ticket #227, reported with initial patch by dfraser) ๐ชต - fix exception if environment contains an invalid locale setting (ticket #200) ๐ญ
- use
cPickleinstead ofpicklefor 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
initandupdatecommands now support the--no-wrapoption (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 adatetimeparameter (ticket #282, patch from Xavier Morel โ thank you ๐ซถ)format_decimal()also got fixed for smallDecimalvalues (ticket #214, patch from George Lund โจ)- messages containing a literal
'\n'are handled properly now (ticket #198) โ no more chaos ๐ - irregular multi-line
msgstrentries โ the ones with no empty""as the first line โ are handled gracefully (ticket #171) ๐ parse_decimal()now returnsDecimalobjects instead of floats โ yes, itโs an API change, so heads up (ticket #178) โ ๏ธ- running
setup.pywithout installedsetuptoolsno longer throws warnings (ticket #262) ๐ธ Locale.__eq__got modified soLocales are only equal if all their attributes โlanguage,territory,script,variantโ are equal ๐- if
pkg_resourcesis installed but noegg-infowas found, it resorts to hard-coded message extractors/checkers (ticket #230) ๐ฅ format_time()andformat_datetime()now accept floats too (ticket #242) ๐- added
babel.support.NullTranslations, a class similar togettext.NullTranslationsbut with all of Babelโs new gettext methods (ticket #277) ๐ initandupdatecommands now support the--widthoption (ticket #284) ๐- the
input_dirsoption for setuptools integration got fixed (ticket #232, initial patch by รtienne Bersac) ๐ซถ - the
.mofile header now contains the same information as the source.pofile (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
pytzand 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
likelySubtagresolving 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-Dateof the catalog being updated equal to thePOT-Creation-Dateof 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-Dateif it is not the default value (ticket #148) ๐พ - made
--no-wrapwork by reworking--widthโs default and mimickingxgettextโs behaviour of always wrapping comments (ticket #145) ๐ - fixed negative offset handling of
Catalog._set_mime_headers(ticket #165) ๐ง - added
--projectand--versionoptions for commandline (ticket #173) ๐๏ธ - added a
__ne__()method to theLocalclass ๐ง - explicitly sorts instead of using
sorted()and doesnโt assume ordering (Python 2.3 and Jython compatibility) ๐ - removed
ValueErrorraising 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
altordraftitems (ticket #217) ๐ข - fixed bad check in
format_time(ticket #257, reported with patch and tests by jomae) ๐ต๏ธ - fixed so
frontend.CommandLineInterface.rundoes 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 againstlen(message.id)because this is always2โ instead, it should be checked againstcatalog.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
@euroare now stripped from locale identifiers when parsing (ticket #136) ๐ถ - the system locales
CandPOSIXare now treated as aliases foren_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
Translationclass inbabel.supportcan now manage catalogs with different message domains, and exposes the family ofd*gettextfunctions (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
datetimeandtimevalues ๐ฐ๏ธ - added a
get_pluralfunction into thepluralsmodule 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
gettexttongettext, or vice versa ๐ - fixed time formatting for
12 amand12 pm๐ - fixed output encoding of the
pybabel --list-localescommand ๐ - MO files are now written in binary mode on Windows (ticket #61) ๐ช
โจ Version 0.9 โจ
(released on August 20th 2007)
- the
new_catalogdistutils command has been renamed toinit_catalogfor 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
Decimalobjects (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 tostderr๐จ - numerous Python message extractor fixes: it now handles nested function calls within a
gettextfunction 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-Languageheader, and sometimes even the incorrect language code ๐
โจ Version 0.8.1 โจ
(released on July 2nd 2007)
default_locale()would fail when the value of theLANGUAGEenvironment variable contained multiple language codes separated by colon, as is explicitly allowed by the GNU gettext tools. As thedefault_locale()function is called at the module level in some modules, this bug would completely break importing these modules on systems whereLANGUAGEis 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
pybabelcommand which prints out all available locales (ticket #24) ๐ป - the name of the command-line script has been changed from just
babeltopybabelto 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
COPYRIGHT ๐
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
- fedora 43: babel 2.18.0-1.fc43 ยท BSD-3-Clause
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms