Of course! Let's break down how to use Python's gettext module for internationalization (i18n) and localization (l10n), often referred to as "translation" or "i18n" for short.

This is the standard, built-in way to handle translations in Python applications, including GUI toolkits like Tkinter, wxPython, and even web frameworks like Django.
The Core Concept: The Workflow
The gettext workflow is designed to separate your application's source code from its human-readable text. Here's the high-level process:
- Mark Strings: In your Python code, you identify all the user-facing strings (like button labels, error messages, etc.) and mark them for translation.
- Generate Template: You use a tool to extract these marked strings from your code and create a "template" file (
.pot). - Create Translations: You use the template file to create a "translation" file (
.po) for each language you want to support (e.g.,fr.pofor French,es.pofor Spanish). - Compile Translations: You compile the
.pofiles into machine-readable binary files (.mo). - Use in Code: Your Python application loads the appropriate
.mofile based on the user's language and automatically translates the marked strings.
Step-by-Step Example
Let's create a simple command-line application to demonstrate the entire process.
Step 1: Project Structure
First, set up a clean project directory structure. This is a best practice.

my_project/
├── locales/
│ ├── fr/
│ │ └── LC_MESSAGES/
│ └── es/
│ └── LC_MESSAGES/
├── src/
│ └── my_app.py
└── setup.py
locales/: This directory will hold all our translation files.fr/andes/: These are the language codes for French and Spanish. TheLC_MESSAGESsubdirectory is the standard location for message catalogs.src/: This will contain our application's source code.
Step 2: Mark Strings for Translation in Python Code
In your Python script (src/my_app.py), you need to import gettext and mark the strings. There are two common ways to do this.
Method A: The Simple gettext() Approach
This is the most straightforward method for beginners.
# src/my_app.py
import gettext
# Define the location of the translation files
localedir = '../locales'
# Set up the translation for a specific language
# We'll change this part later to make it dynamic
french = gettext.translation('my_app', localedir=localedir, languages=['fr'])
french.install() # This makes _() available globally in the module
# The _() function is now a "translator" function
# It will look up the string in the loaded .mo file
hello_msg = _("Hello, world!")
goodbye_msg = _("Goodbye!")
prompt_msg = _("Please enter your name:")
name = input(prompt_msg)
print(f"{hello_msg} {name}!")
print(goodbye_msg)
Method B: The Object-Oriented Approach (Recommended for larger apps)

This method is more explicit and avoids conflicts, especially in larger applications or libraries.
# src/my_app.py (Object-Oriented version)
import gettext
# Define the location of the translation files
localedir = '../locales'
# Create a "translation object" for French
french_translations = gettext.translation('my_app', localedir=localedir, languages=['fr'])
# Create a global translator function from the object
_ = french_translations.gettext
# Now use the _ function exactly as before
hello_msg = _("Hello, world!")
goodbye_msg = _("Goodbye!")
prompt_msg = _("Please enter your name:")
name = input(prompt_msg)
print(f"{hello_msg} {name}!")
print(goodbye_msg)
The gettext Convention: _()
You will almost always see the translator function aliased to _ (a single underscore). This is a widely accepted convention that makes the code more readable. It looks like a function call, but it's just a variable name.
Step 3: Generate the .pot Template File
You need a tool to extract all the strings wrapped in _() from your Python files. The standard tool for this is xgettext, which is part of the GNU gettext utilities. If you don't have it, you may need to install it (e.g., sudo apt-get install gettext on Debian/Ubuntu).
Run this command from your my_project root directory:
xgettext -d my_app -o locales/my_app.pot src/my_app.py
-d my_app: Sets the "domain" name. This name will be used for your.poand.mofiles (e.g.,my_app.po,my_app.mo).-o locales/my_app.pot: Specifies the output file for the template.src/my_app.py: The source file to scan.
This command creates locales/my_app.pot. It will look something like this:
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-10-27 10:00+0000\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" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/my_app.py:10 msgid "Hello, world!" msgstr "" #: src/my_app.py:11 msgid "Goodbye!" msgstr "" #: src/my_app.py:12 msgid "Please enter your name:" msgstr ""
Step 4: Create and Edit .po Translation Files
Now, copy the template file for each language you want to support.
# Create French translation file cp locales/my_app.pot locales/fr/LC_MESSAGES/my_app.po # Create Spanish translation file cp locales/my_app.pot locales/es/LC_MESSAGES/my_app.po
Now, edit these files. Open locales/fr/LC_MESSAGES/my_app.po and translate the msgid strings into the msgstr strings.
# French translation file: locales/fr/LC_MESSAGES/my_app.po # (Header information is kept as is, but 'Language' is changed) msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-10-27 10:00+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: French <fr@li.org>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/my_app.py:10 msgid "Hello, world!" msgstr "Bonjour, le monde !" #: src/my_app.py:11 msgid "Goodbye!" msgstr "Au revoir !" #: src/my_app.py:12 msgid "Please enter your name:" msgstr "Veuillez entrer votre nom :"
Do the same for locales/es/LC_MESSAGES/my_app.po with Spanish translations.
Step 5: Compile .po Files to .mo Files
Python's gettext module needs the binary .mo format. Use the msgfmt utility (also part of the gettext package) to compile them.
# Compile French msgfmt -o locales/fr/LC_MESSAGES/my_app.mo locales/fr/LC_MESSAGES/my_app.po # Compile Spanish msgfmt -o locales/es/LC_MESSAGES/my_app.mo locales/es/LC_MESSAGES/my_app.po
Your directory structure now looks like this:
my_project/
├── locales/
│ ├── fr/
│ │ └── LC_MESSAGES/
│ │ ├── my_app.po
│ │ └── my_app.mo <-- The compiled file!
│ └── es/
│ └── LC_MESSAGES/
│ ├── my_app.po
│ └── my_app.mo <-- The compiled file!
└── src/
└── my_app.py
Step 6: Run the Application
Now you can run your Python script. It will automatically use the translations.
# From the my_project directory, run the script python src/my_app.py
Expected Output (if your system locale is French or you hardcoded it):
Veuillez entrer votre nom : > Jean
Bonjour, le monde ! Jean!
Au revoir !
Making it Dynamic (Detecting User Language)
Hardcoding the language in french = gettext.translation(...) is not practical. A real application should detect the user's preferred language.
You can do this by setting the LANG environment variable before running the script, or by letting gettext fall back to the system's default locale.
Here's how to modify src/my_app.py to be dynamic:
# src/my_app.py (Dynamic version)
import gettext
import os
# Define the location of the translation files
localedir = '../locales'
# Try to get the language from the environment variable LANG
# e.g., 'fr_FR.UTF-8' -> 'fr'
language = os.environ.get('LANG', 'en').split('_')[0]
try:
# Try to load the translation for the detected language
translations = gettext.translation('my_app', localedir=localedir, languages=[language])
translations.install()
except FileNotFoundError:
# If the .mo file for the language doesn't exist, fall back to English
# (or no translation, which just shows the original msgid)
print(f"Warning: Translation for '{language}' not found. Using default.")
_ = gettext.gettext
else:
# If successful, get the _ function
_ = translations.gettext
# The rest of the code remains the same
hello_msg = _("Hello, world!")
goodbye_msg = _("Goodbye!")
prompt_msg = _("Please enter your name:")
name = input(prompt_msg)
print(f"{hello_msg} {name}!")
print(goodbye_msg)
Now you can test it by setting the environment variable:
# Run with French locale LANG=fr_FR.UTF-8 python src/my_app.py # Run with Spanish locale LANG=es_ES.UTF-8 python src/my_app.py # Run with a locale that has no translation (falls back to English) LANG=de_DE.UTF-8 python src/my_app.py
For GUI Toolkits (wxPython, Tkinter)
The principle is exactly the same. You just need to ensure you call gettext.install() or create the _ function before you create any GUI widgets.
Example with Tkinter:
import gettext
import tkinter as tk
localedir = '../locales'
language = os.environ.get('LANG', 'en').split('_')[0]
try:
translations = gettext.translation('my_app', localedir=localedir, languages=[language])
translations.install() # This makes _() available globally
except FileNotFoundError:
print(f"Warning: Translation for '{language}' not found.")
_ = gettext.gettext
else:
_ = translations.gettext
root = tk.Tk()_("My Application"))
label = tk.Label(root, text=_("Hello, world!"))
label.pack(pady=20)
button = tk.Button(root, text=_("Click Me"))
button.pack(pady=10)
root.mainloop()
This comprehensive guide should give you everything you need to start internationalizing your Python applications using the standard gettext module.
