polib
polib is a pure Python library designed to manipulate gettext files, specifically .po (Portable Object) and .mo (Machine Object) files. It enables loading, creating, modifying, and saving these translation files. The library is stable, widely used, and supports Python versions from 2.7 to the latest 3.x. Its release cadence is irregular but indicates active maintenance, with version 1.2.0 released in February 2023.
Common errors
-
ModuleNotFoundError: No module named 'polib'
cause The 'polib' library is not installed in your current Python environment.fixInstall the library using pip: `pip install polib` -
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x... in position ...: invalid start byte
cause The PO/MO file you are trying to read or write is not encoded in UTF-8, or has an encoding mismatch, causing Python to fail decoding specific byte sequences.fixExplicitly specify the correct encoding when loading or saving the file, often after detecting it with `polib.detect_encoding()`. Example: `po = polib.pofile('path/to/file.po', encoding='latin-1')` -
TypeError: list indices must be integers or slices, not str
cause This error often occurs when incorrectly trying to assign an empty string directly to `POEntry.msgstr_plural` or attempting to access plural forms with string indices, while it expects a dictionary or a list of strings.fixAssign `msgstr_plural` an empty dictionary `{}` or a list of empty strings `['', '']` (depending on plural forms) when initializing or clearing plural translations. Example: `entry.msgstr_plural = {'0': ''}` or `entry.msgstr_plural = ['', '']`.
Warnings
- breaking Support for Python versions older than 2.7 was dropped in version 1.1.1.
- gotcha Enabling `check_for_duplicates=True` when adding entries to a POFile can significantly slow down performance, especially with large files.
- gotcha Refactoring of `POEntry.__cmp__` method in version 1.1.0 might affect custom sorting logic or assumptions about entry comparison.
- gotcha Prior to version 1.1.1, `polib` might not correctly handle Message Context (`msgctxt`) in MO files. Additionally, older versions (<=1.0.3) could raise exceptions during `POFile.append()` with `check_for_duplicates=True` if `msgid` was the same but `msgctxt` differed.
- gotcha How `polib` wraps 'occurrences' (the `#: reference...` lines) might differ from `xgettext --no-wrap` output, potentially leading to inconsistencies if strict byte-for-byte matching is expected.
Install
-
pip install polib
Imports
- polib
import polib
- pofile
po = polib.pofile('path/to/catalog.po') - mofile
mo = polib.mofile('path/to/catalog.mo') - POFile
new_po = polib.POFile()
- POEntry
entry = polib.POEntry(msgid='Hello', msgstr='Hola')
Quickstart
import polib
import os
# Create a dummy .po file for demonstration
dummy_po_content = '''\
msgid ""
msgstr ""
"Project-Id-Version: test\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n"
"X-Generator: Python-polib\n"
#: main.py:10
msgid "Hello, world!"
msgstr ""
#: another.py:5
msgid "Another message"
msgstr "Other message"
'''
# Load an existing PO file (or from a string)
# For a real file, use: pofile = polib.pofile('/path/to/your/file.po')
pofile = polib.pofile(dummy_po_content)
print("\n--- Iterating through entries ---")
for entry in pofile:
print(f"Msgid: {entry.msgid}, Msgstr: {entry.msgstr}")
# Add a new entry
new_entry = polib.POEntry(
msgid='New string',
msgstr='Nueva cadena',
comment='A new comment for this string',
occurrences=[('app.py', '20')]
)
pofile.append(new_entry)
print("\n--- After adding a new entry ---")
for entry in pofile:
print(f"Msgid: {entry.msgid}, Msgstr: {entry.msgstr}")
# Modify an entry
if pofile.find('Hello, world!'):
entry_to_modify = pofile.find('Hello, world!')
entry_to_modify.msgstr = '¡Hola, mundo!'
entry_to_modify.comment = 'Translated by quickstart'
print("\n--- After modifying an entry ---")
for entry in pofile:
print(f"Msgid: {entry.msgid}, Msgstr: {entry.msgstr}")
# Save the modified PO file (to a dummy path for demonstration)
dummy_output_path = 'temp_output.po'
pofile.save(dummy_output_path)
print(f"\nSaved modified PO file to {dummy_output_path}")
# Clean up the dummy file
os.remove(dummy_output_path)
print(f"Cleaned up {dummy_output_path}")