Software localization
The Ultimate Guide to Python Localization

Localizing Python applications is a process with many moving parts. But when done right, it can improve accessibility, increase user engagement, and expand market reach.
To help you get started, this tutorial will outline best practices for preparing Python apps for localization using JSON or YAML files, and discuss common modules available to help.
It will provide valuable insights for both developers looking to improve global appeal and businesses looking to expand into new markets.
How do I internationalize UI strings with Python?
Let’s address some key questions on how best to prepare your Python app for localization.
Why do we use translation files for our UI?
There are a couple of benefits to using translation files instead of hard-coding translation texts in your application:
- It is easier to edit and translate as most translators are not familiar with code
- You can easily identify the wrong translation texts without searching through your code base
- There is no need to rebuild your applications when there is a change to a translation (depending on programming languages and frameworks used)
At its core, translation files can be represented in different formats. The following section covers how to internationalize your Python application using:
- Custom implementation with JSON / YAML as translation files
babel
andgettext
to work with PO and MO translation files
Installation
Before that, let’s install the following Python packages:
babel
: utilities for internationalization and localization in Pythonpyyaml
: support reading and writing of YAML files
We can run the following commands from the command line to install our packages.
pip install pyyaml
pip install babel
How to load JSON translation files?
One of the simplest methods to store translation files is via JSON. For example, you can create a new file called en.json
with the following items:
{
"title": "Dialogstation",
"ques-name": "Geben Sie Ihren Namen ein:",
"ques-age": "Geben Sie Ihr Alter ein:",
"ans-name": "Hallo, $name! Willkommen bei Phrase",
"ans-age": {
"one": "Du bist $count Jahr alt",
"other": "Du bist $count Jahre alt"
},
"ques-dob": "Geben Sie Ihr Geburtsdatum ein (JJJJ-MM-TT):",
"ans-dob": "Sie wurden am $dob geboren"
}
Code language: JSON / JSON with Comments (json)
Then, simply load it using the built-in json
package as follows:
import json
with open('filename.json', 'r', encoding='utf8') as f:
data = json.load(f)
Code language: JavaScript (javascript)
How to load YAML translation files?
We can use YAML files instead of JSON. We will need the pyyaml
package to support YAML.
Given en.yaml
as the translation file for English with the following content:
title: Interactive Terminal
ques-name: "Enter your name:"
ques-age: "Enter your age:"
ans-name: Hello, $name! Welcome to Phrase
ans-age:
one: You are $count year old
other: You are $count years old
ques-dob: "Enter your date of birth (YYYY-MM-DD):",
ans-dob: You were born on $dob
Code language: PHP (php)
Likewise, the German translation file should be called de.yaml
:
title: Dialogstation
ques-name: "Geben Sie Ihren Namen ein:"
ques-age: "Geben Sie Ihr Alter ein:"
ans-name: Hallo, $name! Willkommen bei Phrase
ans-age:
one: Du bist $count Jahr alt
other: Du bist $count Jahre alt
ques-dob: "Geben Sie Ihr Geburtsdatum ein (JJJJ-MM-TT):",
ans-dob: Sie wurden am $dob geboren
Code language: PHP (php)
Load it using pyyaml
package as follows:
import yaml
with open('filename.yml', 'r', encoding='utf8') as f:
data = yaml.safe_load(f)import json
Code language: JavaScript (javascript)
Translator class
Using what you have learned so far, let’s build a custom module with a Translator
class with the following features:
- Load translation files during startup
- Set the currently active locale
- Set the plural rules
- Translate and format the translation text
In addition, the module should also include 2 additional functions to:
- Convert a string to a datetime object
- Format a datetime object to a string
Make sure that your file structure is as follows:
.
├── data (folder containing JSON or YAML translation files)/
│ ├── en.json
│ ├── de.json
│ ├── en.yaml
│ └── de.yaml
├── i18n.py (custom module)
└── main.py (main application)
Code language: plaintext (plaintext)
Create a new Python file called i18n.py
with the following code:
import json
import glob
import os
import yaml
from datetime import datetime
from babel.dates import format_datetime
supported_format = ['json', 'yaml']
class Translator():
def __init__(self, translations_folder, file_format='json', default_locale='en'):
# initialization
self.data = {}
self.locale = 'en'
# check if format is supported
if file_format in supported_format:
# get list of files with specific extensions
files = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
for fil in files:
# get the name of the file without extension, will be used as locale name
loc = os.path.splitext(os.path.basename(fil))[0]
with open(fil, 'r', encoding='utf8') as f:
if file_format == 'json':
self.data[loc] = json.load(f)
elif file_format == 'yaml':
self.data[loc] = yaml.safe_load(f)
def set_locale(self, loc):
if loc in self.data:
self.locale = loc
else:
print('Invalid locale')
def get_locale(self):
return self.locale
def translate(self, key):
# return the key instead of translation text if locale is not supported
if self.locale not in self.data:
return key
text = self.data[self.locale].get(key, key)
return text
def str_to_datetime(dt_str, format='%Y-%m-%d'):
return datetime.strptime(dt_str, format)
def datetime_to_str(dt, format='MMMM dd, yyyy', loc='en'):
return format_datetime(dt, format=format, locale=loc)
The glob
module is used to load the translation files dynamically depending on the file_format
value. It accepts either json
or yaml
:
def __init__(self, folder, file_format='json', default_locale='en', default_locale='en'):
# ...
# check if format is supported
if file_format in supported_format:
# get list of files with specific extensions
files = glob.glob(os.path.join(folder, f'*.{file_format}'))
for fil in files:
# get the name of the file without extension, will be used as locale name
loc = os.path.splitext(os.path.basename(fil))[0]
with open(fil, 'r', encoding='utf8') as f:
if file_format == 'json':
self.data[loc] = json.load(f)
elif file_format == 'yaml':
self.data[loc] = yaml.safe_load(f)
Code language: PHP (php)
The core feature is the translate
function which returns the translation text based on the current active locale:
def translate(self, key):
# return the key instead
if self.locale not in self.data:
return key
text = self.data[self.locale].get(key, key)
return text
Code language: PHP (php)
The module also contains 2 additional global functions for datetime formatting:
def str_to_datetime(dt_str, format='%Y-%m-%d'):
return datetime.strptime(dt_str, format)
def datetime_to_str(dt, format='MMMM dd, yyyy', loc='en'):
return format_datetime(dt, format=format, locale=loc)
Code language: JavaScript (javascript)
Adding interpolation to the Translator class
For string interpolation, you can use the built-in template string module. Add the following import statement:
from string import Template
Code language: JavaScript (javascript)
Under the translate
function, instead of returning the text
variable directly, instantiate it with the Template
class and call the safe_substitute
function as follows:
def translate(self, key, **kwargs):
# return the key instead of translation text if locale is not supported
if self.locale not in self.data:
return key
text = self.data[self.locale].get(key, key)
# string interpolation
return Template(text).safe_substitute(**kwargs)
Code language: PHP (php)
Adding pluralization to the Translator class
You can import the babel.plural.PluralRule
class for pluralization support:
from babel.plural import PluralRule
Code language: JavaScript (javascript)
Under the __init__
function, initialize a new plural_rule
variable as follows:
def __init__(self, folder, file_format='json', default_locale='en'):
self.data = {}
self.locale = default_locale
self.plural_rule = PluralRule({'one': 'n is
Code language: PHP (php)
🗒 Note » The module uses {'one': 'n is 1'}
as the plural rule, which represents simple pluralization. The key should be one of the following:
- 0
- 1
- 2
- a few
- many
- other
🔗 Resource » The pluralization syntax is based on the CDLR rules.
Next, implement the setter and getter function for the plural rule in the Translator
class:
class Translator():
...
def set_locale(self, loc):
if loc in self.data:
self.locale = loc
else:
print('Invalid locale')
def get_locale(self):
return self.locale
def set_plural_rule(self, rule):
try:
self.plural_rule = PluralRule(rule)
except Exception:
print('Invalid plural rule')
def get_plural_rule(self):
return self.plural_rule
Modify the code under the translate
function to include a check for pluralization:
class Translator():
...
def translate(self, key, **kwargs):
# return the key instead of translation text if locale is not supported
if self.locale not in self.data:
return key
text = self.data[self.locale].get(key, key)
# type dict represents key with plural form
if type(text) == dict:
count = kwargs.get('count', 1)
# parse count to int
try:
count = int(count)
except Exception:
print('Invalid count')
return key
text = text.get(self.plural_rule(count), key)
return Template(text).safe_substitute(**kwargs)
The complete code for the Translator
class is as follows:
from babel.plural import PluralRule
import json
from string import Template
import glob
import os
import yaml
from datetime import datetime
from babel.dates import format_datetime
supported_format = ['json', 'yaml']
class Translator():
def __init__(self, translations_folder, file_format='json', default_locale='en'):
# initialization
self.data = {}
self.locale = 'en'
self.plural_rule = PluralRule({'one': 'n is 1'})
# check if format is supported
if file_format in supported_format:
# get list of files with specific extensions
files = glob.glob(os.path.join(translations_folder, f'*.{file_format}'))
for fil in files:
# get the name of the file without extension, will be used as locale name
loc = os.path.splitext(os.path.basename(fil))[0]
with open(fil, 'r', encoding='utf8') as f:
if file_format == 'json':
self.data[loc] = json.load(f)
elif file_format == 'yaml':
self.data[loc] = yaml.safe_load(f)
def set_locale(self, loc):
if loc in self.data:
self.locale = loc
else:
print('Invalid locale')
def get_locale(self):
return self.locale
def set_plural_rule(self, rule):
try:
self.plural_rule = PluralRule(rule)
except Exception:
print('Invalid plural rule')
def get_plural_rule(self):
return self.plural_rule
def translate(self, key, **kwargs):
# return the key instead of translation text if locale is not supported
if self.locale not in self.data:
return key
text = self.data[self.locale].get(key, key)
# type dict represents key with plural form
if type(text) == dict:
count = kwargs.get('count', 1)
# parse count to int
try:
count = int(count)
except Exception:
print('Invalid count')
return key
text = text.get(self.plural_rule(count), key)
return Template(text).safe_substitute(**kwargs)
def parse_datetime(dt, input_format='%Y-%m-%d', output_format='MMMM dd, yyyy', output_locale='en'):
dt = datetime.strptime(dt, input_format)
return format_datetime(dt, format=output_format, locale=output_locale)
Using the Translator module in your application
Now, you can use the Translator
class as a module in your application. Have a look at the following code snippet as a reference for the basic usage of the custom Translator
module:
# import the module
import i18n
# instantiate a new Translator class with the path to the data
translator = i18n.Translator('data/')
Code language: PHP (php)
name = 'John Doe'
print(translator.translate('ans-name', name=name))
# Hello, John Doe! Welcome to Phrase
# change the active locale to de
translator.set_locale('de')
print(translator.translate('ans-name', name=name))
# Hallo, John Doe! Willkommen bei Phrase
age = 30
print(translator.translate('ans-age', count=age))
# Du bist 30 Jahre alt
dob = '1992-01-01'
dob = i18n.parse_datetime(dob)
print(translator.translate('ans-dob', dob=dob))
# Sie wurden am January 01, 1992 geboren
Code language: PHP (php)
You can easily extend the functionality of the i18n
module to support more features based on your requirements.
How do I internationalize my Python UI with gettext?
Alternatively, you can utilize the gettext
module to internationalize Python applications. gettext
uses PO (also known as POT) and MO message catalog files.
🗒 Note » PO files represent the human-editable translation files, while MO files are machine-readable for consumption by gettext
.
Fortunately, the babel
package complements nicely with the gettext
module. babel
provides the following utility functions for working with Message Catalogs:
extract
: extract messages from source files to generate a POT fileinit
: create new message catalogs from a POT fileupdate
: update existing message catalogs in a POT filecompile
: compile POT files to MO files
Basic translation messages
The first phase is to mark strings as translatable in the source files. Simply enclose translatable strings with the _()
function:
# unmarked strings
print('Interactive Terminal')
print('title')
# strings marked as translatable
print(_('Interactive Terminal'))
print(_('title'))
Code language: PHP (php)
🗒 Note » _()
is a short-hand alias of gettext.gettext()
function. You can pass in the key for translation or the actual translation string as long as it is unique across the application. It will return the translated string based on the current locale.
For example, given that the code above resides in a Python file called main_babel.py
, the command to extract messages from source files is as follows:
pybabel extract -o data/messages.pot main_babel.py
The output file, data/messages.pot
, should contain the following content:
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-06-17 23:04+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.10.1\n"
#: main_babel.py:11
msgid "title"
msgstr ""
#: main_babel.py:14
msgid "ques-name"
msgstr ""
#
Code language: PHP (php)
It represents the base translation file. msgid
is the unique identifier for each message to be translated, while msgstr
represents the translation text. You can leave msgstr
empty for now as the actual translation text should be in the PO files.
The babel
module will generate the relevant files for each locale automatically for you during the initialization phase. Let’s test it by running the following command for the en
locale.
pybabel init -l en -i data/messages.pot -d data/
🗒 Note » -l sets the locale for the generated PO files.
Run another command again for de
locale.
pybabel init -l de -i data/messages.pot -d data/
The content inside each messages.po
file should be the same as the base POT file. The next step is to fill in the corresponding translation text inside the PO files:
.
├── messages.pot
├── de/
│ └── LC_MESSAGES/
│ └── messages.po
└── en/
└── LC_MESSAGES/
└── messages.po
Code language: plaintext (plaintext)
#: main_babel.py:11
msgid "title"
msgstr "Interactive Terminal"
#: main_babel.py:14
msgid "ques-name"
msgstr "Enter your name:"
#: main_babel.py:16
msgid "ans-name"
msgstr "Hello, {name}! Welcome to Phrase"
#: main_babel.py:19
msgid "ques-age"
msgstr "Enter your age:"
#: main_babel.py:21
msgid "ans-age"
msgid_plural "ans-age-plural"
msgstr[0] "You are {count} year old"
msgstr[1] "You are {count} years old"
Code language: PHP (php)
Finally, run the following command to compile PO files into MO files:
pybabel compile -d data/
The data
folder should be as follows:
.
├── messages.pot
├── de/
│ └── LC_MESSAGES/
│ ├── messages.mo
│ └── messages.po
└── en/
└── LC_MESSAGES/
├── messages.mo
└── messages.po
Code language: plaintext (plaintext)
Once you are done with it, you can utilize the gettext.translation
function to load the translation files.
For example, you can call the install
function to set it as the currently active locale and get the translation text as follows:
import gettext
# initialization
lang_en = gettext.translation('messages', localedir='data', languages=['en'])
# set current locale to en
lang_en.install()
print(_('ans-name'))
# Hello, John! Welcome to Phrase
Code language: PHP (php)
🗒 Note » The install function will import the _()
alias internally. Hence, there is no need to import _
manually.
Adding string interpolation via gettext
For string interpolation, you can use a variable name and mark it with curly brackets. For example, given the following translation text:
#: main_babel.py:16
msgid "ans-name"
msgstr "Hello, {name}! Welcome to Phrase"
Code language: PHP (php)
🗒 Note » Using curly brackets {variable_name}
in the translation text allows string interpolation with the format
function.
You can easily interpolate in the translation text as follows:
import gettext
# initialization
lang_en = gettext.translation('messages', localedir='data', languages=['en'])
# set current locale to en
lang_en.install()
print(_('ans-name').format(name='John'))
# Hello, John! Welcome to Phrase
print(_('ans-name').format(name='Kelly'))
# Hello, Kelly! Welcome to Phrase
Code language: PHP (php)
Adding pluralization support via ngettext
For pluralization support, use the ngettext
function instead. It accepts 3 input arguments:
singular
: id for singular formplural
: id for plural formn
: plural determiner
print(ngettext('ans-age', 'ans-age-plural', age))
Code language: PHP (php)
Just like the gettext
function, ngettext
will return the translated string. You can easily interpolate in the output string with a function like format
:
print(ngettext('ans-age', 'ans-age-plural', age).format(count=age))
Code language: PHP (php)
Upon extraction, you should see a different syntax in the base translation file (POT):
#: main_babel.py:21
msgid "ans-age"
msgid_plural "ans-age-plural"
msgstr[0] ""
msgstr[1] ""
Code language: PHP (php)
The number of msgstr
is based on the number of plural forms specified for the locale. Different locales have different amounts of plural forms: While English has 2 plural forms, Arabic has 6.
A comment at the top of the file shows how a locale’s plural form for a message is resolved. For example:
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
Code language: JSON / JSON with Comments (json)
🔗 Resource » The pluralization syntax is based on the CDLR rules.
Have a look at the following examples:
# 2 plural forms
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
# 3 plural forms
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
# 6 plural forms
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : "
"n%100>=3 && n%100<=10 ? 3 : n%100>=0 && n%100<=2 ? 4 : 5)\n"
Code language: HTML, XML (xml)
You can get the pluralized text as follows:
import gettext
# initialization
lang_en = gettext.translation('messages', localedir='data', languages=['en'])
# set current locale to en
lang_en.install(names=['gettext', 'ngettext'])
print(ngettext('ans-age', 'ans-age-plural', age).format(count=1))
# You are 1 year old
print(ngettext('ans-age', 'ans-age-plural', age).format(count=12))
# You are 12 years old
Code language: PHP (php)
Using gettext in your application
Instead of loading each locale one by one, you can load the translation files dynamically and store them in a dict
object as follows:
import gettext
translations = {}
supported_langs = ['en', 'de']
# load translation files dynamically
for lang in supported_langs:
translations[lang] = gettext.translation('messages', localedir='data', languages=[lang])
Code language: PHP (php)
Now, simply set the desired locale and call either the _
or ngettext
functions to get the desired translation texts:
import gettext
translations = {}
supported_langs = ['en', 'de']
# load translation files dynamically
for lang in supported_langs:
translations[lang] = gettext.translation('messages', localedir='data', languages=[lang])
# set active locale to en
translations['en'].install(names=['gettext', 'ngettext'])
name = 'John Doe'
print(_('ans-name').format(name=name))
# Hello, John Doe! Welcome to Phrase
# change the active locale to de
translations['de'].install(names=['gettext', 'ngettext'])
print(_('ans-name').format(name=name))
# Hallo, John Doe! Willkommen bei Phrase
age = 30
text = ngettext('ans-age', 'ans-age-plural', age)
print(text)
# Du bist 30 Jahre alt
text = text.format(count=age)
print(text)
# Du bist 30 Jahre alt
Code language: PHP (php)
Simply run the file as usual and you should get the same output as what we did previously using JSON or YAML.
🤿 Go deeper » Check out our guide to translating Python applications with the GNU gettext module to learn more about the gettext
module.
How can a Python app support right-to-left languages?
Python does not come with an out-of-the-box implementation for displaying text meant for right-to-left languages, but you can utilize the python-bidi
module for such use cases. python-bidi
is a Python implementation of a bi-directional (BiDi) layout. It provides a convenient function to display right-to-left languages. Run the following command to install it:
pip install python-bidi
It comes with the get_display
function to display a string bi-directionally:
from bidi.algorithm import get_display
get_display('اَلْعَرَبِيَّةُ')
Code language: JavaScript (javascript)
You should get the following output when you print the result:
اَلْعَرَبِيَّةُ
How do I format localized dates and times in Python?
Python has its own built-in datetime
module for manipulating dates and times. It supports arithmetic operations on objects, making it extremely useful for localization and internationalization.
How do I work with date and time duration?
A timedelta
object represents the difference between 2 dates or times. You can think of it as a duration. It accepts the following input arguments:
datetime.timedelta(milliseconds=0, microseconds=0, seconds=0, minutes=0, hours=0, days=0, weeks=0)
You can create a new timedelta
as follows:
from datetime import timedelta
delta = timedelta(
seconds=27,
minutes=5,
hours=8,
days=50,
weeks=2
)
# 64 days, 8:05:27
Code language: PHP (php)
🤿 Go deeper » Check out the Timedelta objects documentation to learn more about the Timedelta
class.
How can I use datetimes?
A time
object represents a local time of the day regardless of the date, while a date
represents a date in a calendar. Often the ideal choice is to use the datetime
object. It contains all the relevant information related to time and date. The datetime
object can be categorized as naive
or aware
, depending on whether it contains information related to timezone.
🤿 Go deeper » Check out the Aware and Naive Objects documentation to learn more about them.
from datetime import datetime, timedelta, timezone
# naive datetime obj
dt = datetime.now()
# 2022-06-12 15:48:40.014838
# aware datetime obj (GMT+8)
tz = timezone(timedelta(hours=8))
dt = datetime.now(tz=tz)
# 2022-06-12 15:48:40.014838+08:00
Code language: PHP (php)
A datetime
object supports the following methods for datetime conversion:
strftime
: convert adatetime
object to a string according to a given formatstrptime
: parse a string into adatetime
object given a corresponding format
The strftime
function accepts a string, which indicates the corresponding date and time format. This comes in handy when localizing the content of an application.
🤿 Go deeper » Check out the strftime and strptime documentation to learn more about them.
Have a look at the following code snippet which displays different string output depending on the input format string.
from datetime import datetime, timezone
dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)
dt.strftime('%Y-%m-%d %H:%M:%S')
# 2022-06-12 16:32:45
dt.strftime('%b %d, %Y')
# Jun 12, 2022
dt.strftime('%A (%I.%M %p)')
# Sunday (04.32 PM)
dt.strftime('%c')
# Sun Jun 12 16:32:45 2022
Code language: PHP (php)
The format depends on the existing locale of the application. You can use the built-in locale
module to change it.
import locale
# get current locale
locale.getlocale()
# ('English_Singapore', '1252')
locale.setlocale(locale.LC_ALL, 'de_DE')
# ('de_DE', 'ISO8859-1')
Code language: PHP (php)
🤿 Go deeper » Check out our beginner’s guide to Python’s locale module to learn more about the locale
module.
Let’s test it again using de_DE
(German in Germany) as the current locale instead:
from datetime import datetime, timezone
import locale
locale.setlocale(locale.LC_ALL, 'de_DE')
dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)
dt.strftime('%Y-%m-%d %H:%M:%S')
# 2022-06-12 16:32:45
dt.strftime('%b %d, %Y')
# Jun 12, 2022
dt.strftime('%A (%I.%M %p)')
# Sonntag (04.32 )
dt.strftime('%c')
# 12.06.2022 16:32:45
Code language: PHP (php)
On the other hand, the strptime
function takes in 2 input arguments:
date_string
: a string representation of a datetime based on 1989 C standard.format
: the format to parse the inputdate_string
🗒 Note » The strptime
function cares about the current locale when parsing the input.
from datetime import datetime
text = '2022-06-12 16:32:45'
format = '%Y-%m-%d %H:%M:%S'
datetime.strptime(text, format)
# 2022-06-12 16:32:45
Code language: PHP (php)
How do I format localized dates with Babel?
Alternatively, you can utilize the babel.dates
module to format date and time.
A note on Babel
The babel
module is a collection of utilities for l10n and i18n in Python. It is actively maintained and offers the following features:
- Datetime formatting
- Number formatting
- Currency formatting
- Generating message catalogs (translation files)
🤿 Go deeper » Check out Babel’s i18n advantages for multilingual apps to learn more about the babel
module.
OK, back to date formatting. babel.dates
comes with the following functions:
- format_time
- format_date
- format_datetime
Have a look at the following code snippet for the usage of Babel’s formatting functions:
from datetime import datetime, timezone
from babel.dates import format_time, format_date, format_datetime
dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)
# using the default medium format
format_time(dt, locale='en_US')
# 4:32:45 PM
format_date(dt, locale='en_US')
# Jun 12, 2022
format_datetime(dt, locale='en_US')
# Jun 12, 2022, 4:32:45 PM
# using full format
format_time(dt, format='full', locale='en_US')
# 4:32:45 PM Coordinated Universal Time
format_date(dt, format='full', locale='en_US')
# Sunday, June 12, 2022
format_datetime(dt, format='full', locale='en_US')
# Sunday, June 12, 2022 at 4:32:45 PM Coordinated Universal Time
# using German locale
format_time(dt, format='full', locale='de_DE')
# 16:32:45 Koordinierte Weltzeit
format_date(dt, format='full', locale='de_DE')
# Sonntag, 12. Juni 2022
format_datetime(dt, format='full', locale='de_DE')
# Sonntag, 12. Juni 2022 um 16:32:45 Koordinierte Weltzeit
Code language: PHP (php)
The format
argument is optional and can be one of the following choices:
- Short
- Medium (the default value)
- Long
- Full
🗒 Note » The final output is based on the input locale
argument.
🤿 Go deeper » Check out the babel.dates fields documentation to learn more about custom patterns.
How do I use babel to work with time zones?
You can utilize the get_timezone
function to create a new timezone object based on timezone names such as US/Eastern
or Europe/Berlin
. Then, pass the object as input for tzinfo
argument:
from datetime import datetime, timezone
from babel.dates import get_timezone, format_datetime
dt = datetime(year=2022, month=6, day=12, hour=16, minute=32, second=45, tzinfo=timezone.utc)
eastern = get_timezone('US/Eastern')
berlin = get_timezone('Europe/Berlin')
# using eastern timezone
format_datetime(dt, format='full', locale='en_US', tzinfo=eastern)
# Sunday, June 12, 2022 at 12:32:45 PM Eastern Daylight Time
# using berlin timezone
format_datetime(dt, format=format, locale='en_US', tzinfo=berlin)
# Sunday, June 12, 2022 at 6:32:45 PM Central European Summer Time
Code language: PHP (php)
How do I format localized numbers in Python?
Number formatting can be tricky when it comes to internationalization. For example, the text 12,345
conveys different meanings for English US (en_US
) and German (de_DE
). This is mainly because different languages use different symbols for decimal points and thousand separators.
🤿 Go deeper » Our concise guide to number localization covers grouping, separators, numeral systems, and more.
How do I use the built-in locale module to format numbers?
For conversion from string to integer or floating point, the locale
module is a good option. It comes with the following built-in function:
atoi
: convert a string to an integer using the current locale numeric conventionsatof
: convert a string to a floating point using the current locale numeric conventions
Given 12,345
as the input string, the results of atof
for both languages are as follows:
import locale
# English
locale.setlocale(locale.LC_ALL, 'en_US')
locale.atof('12,345')
# 12345.0
# German
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.atof('12,345')
# 12.345
# English
locale.setlocale(locale.LC_ALL, 'en_US')
locale.atof('12.345')
# 12.345
# German
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.atof('12.345')
# 12345.0
Code language: PHP (php)
On the other hand, you can utilize the format_string
function to convert a number into a localized string. It accepts the following input argument:
format
: a string representing the format specificationval
: a numbergrouping
: whether to take grouping into account. Grouping refers to a sequence of numbers specifying which relative positions the thousand separator is expected. It isFalse
by default.
Have a look at the following code snippet:
import locale
locale.setlocale(locale.LC_ALL, 'en_US')
locale.format_string('%10.2f', 123456.78)
# 123456.78
locale.format_string('%10.2f', 123456.78, grouping=True)
# 123,456.78
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.format_string('%10.2f', 123456.78)
# 123456,78
locale.format_string('%10.2f', 123456.78, grouping=True)
# 123.456,78
Code language: PHP (php)
How do I use babel to format localized numbers?
Alternatively, you can utilize the following locale-specific formatting functions provided by the babel.numbers
module:
format_decimal
: format a given number based on the input localeformat_percent
: format a given number to percentage based on the input localeformat_scientific
: format a given number to scientific notation based on the input locale. It usesE
as the notation for power of 10
The following code snippet illustrates the output for format_decimal
when using different locales:
from babel.numbers import format_decimal, format_percent, format_scientific
format_decimal(12345, locale='en_US')
# 12,345
format_decimal(12345.67, locale='en_US')
# 12,345.67
format_decimal(12345, locale='de_DE')
# 12.345
format_decimal(12345.67, locale='de_DE')
# 12.345,67
format_percent(0.34, locale='en_US')
# 34%
format_percent(0.34, locale='de_DE')
# 34 %
format_scientific(1234567, locale='en_US')
# 1.234567E6
format_scientific(1234567, locale='de_DE')
# 1,234567E6
Code language: PHP (php)
🗒 Note » Unlike the locale.format_string
function, the babel.numbers.format_decimal
function allows parsing of custom patterns.
🤿 Go deeper » Check out the Custom Pattern Syntax documentation to learn more about custom patterns.
How do I format localized currency in Python?
Currency formatting takes into account the currency symbol and locale. For example, the symbols €
and EUR
represent euros and can be used interchangeably.
How do I use the built-in locale module to format currency?
The built-in locale
module comes with the currency
function to format currency. There is an optional international
argument to display the currency using the international name instead of the currency symbol.
How do I use Babel to format localized currency?
The babel.numbers
module comes with the format_currency
function that considers both the locale and currency. It means that you can format a number using de_DE
locale for USD
.
Have a look at the following code snippet to understand more:
import locale
from babel.numbers import format_currency
locale.setlocale(locale.LC_ALL, 'en_US')
locale.currency(1234.56)
# $1234.56
locale.currency(1234.56, international=True)
# USD1234.56
format_currency(1234.56, 'USD', locale='en_US')
# $1,234.56
# set the currency to EURO
format_currency(1234.56, 'EUR', locale='en_US')
# €1,234.56
# set the currency to Japan YEN
format_currency(1234.56, 'JYP', locale='en_US')
# JYP1,234.56
locale.setlocale(locale.LC_ALL, 'de_DE')
locale.currency(1234.56)
# 1234,56 €
locale.currency(1234.56, international=True)
# 1234,56 EUR
format_currency(1234.56, 'USD', locale='de_DE')
# 1.234,56 $
format_currency(1234.56, 'EUR', locale='de_DE')
# 1.234,56 €
format_currency(1234.56, 'JYP', locale='de_DE')
# 1.234,56 JYP
Code language: PHP (php)
🗒 Note » Unlike the locale.format_currency
function, the babel.numbers.format_currency
function allows you to specify the currency explicitly. Instead of having USD bound to en_US
locale, you can display the desired currency via the currency
argument.
Wrapping up
We are done! By now, you should have all the knowledge required to localize and internationalize Python applications. If you are building web applications in Python and looking for built-in localization methods, consider checking out the following articles:
- An I18n Walkthrough for Falcon Web Apps in Python
- A Step-by-Step Tutorial on Python Tornado
- CherryPy I18n Cookbook with Babel and Jinja2
- Pyramid I18n Tutorial From Scratch
- Flask App Tutorial on Internationalization
- Website I18n with Django REST Framework and django-parler
- A Quick Guide to Django i18n
If you want to further streamline your localization process, consider signing up for Phrase, the most connective and customiziable suite of translation automation technology available.
Phrase comes with a 14-day free trial and can provide you with everything you need to:
- Build production-ready integrations with your development workflow
- Invite as many users as you wish to collaborate on your projects
- Edit and convert localization files online with more context for higher translation quality.