CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-polib

A library to manipulate gettext files (po and mo files).

Pending
Overview
Eval results
Files

entry-manipulation.mddocs/

Entry Manipulation

Comprehensive functionality for creating, modifying, finding, and managing translation entries. Includes support for pluralization, context strings, translator comments, and all gettext entry metadata.

Capabilities

Creating Translation Entries

Create new POEntry and MOEntry instances with complete support for all gettext entry features.

class POEntry:
    def __init__(self, msgid='', msgstr='', msgid_plural='', msgstr_plural=None, 
                 msgctxt='', obsolete=False, encoding='utf-8', comment='', 
                 tcomment='', occurrences=None, flags=None, previous_msgctxt='', 
                 previous_msgid='', previous_msgid_plural='', linenum=0):
        """
        Create a new PO entry.

        Parameters:
        - msgid (str): Source message ID
        - msgstr (str): Translated message  
        - msgid_plural (str): Plural source message
        - msgstr_plural (dict): Plural translations {0: str, 1: str, ...}
        - msgctxt (str): Message context
        - obsolete (bool): Whether entry is obsolete
        - encoding (str): Entry encoding
        - comment (str): Generated comment
        - tcomment (str): Translator comment
        - occurrences (list): List of (filename, line_number) tuples
        - flags (list): List of flags (e.g., ['fuzzy', 'python-format'])
        - previous_msgctxt (str): Previous context (for fuzzy entries)
        - previous_msgid (str): Previous msgid (for fuzzy entries)
        - previous_msgid_plural (str): Previous msgid_plural (for fuzzy entries)
        - linenum (int): Line number in source file
        """

class MOEntry:
    def __init__(self, msgid='', msgstr='', msgid_plural='', msgstr_plural=None,
                 msgctxt='', obsolete=False, encoding='utf-8', **kwargs):
        """
        Create a new MO entry (compatibility parameters are ignored).

        Parameters:
        - msgid (str): Source message ID
        - msgstr (str): Translated message
        - msgid_plural (str): Plural source message  
        - msgstr_plural (dict): Plural translations {0: str, 1: str, ...}
        - msgctxt (str): Message context
        - obsolete (bool): Whether entry is obsolete
        - encoding (str): Entry encoding
        """

Usage Examples:

import polib

# Create a simple entry
entry = polib.POEntry(
    msgid='Hello World',
    msgstr='Hola Mundo'
)

# Create entry with context
entry = polib.POEntry(
    msgid='File',
    msgstr='Archivo',
    msgctxt='menu_item'  # Distinguish from other uses of "File"
)

# Create plural entry
entry = polib.POEntry(
    msgid='%d file',
    msgid_plural='%d files',
    msgstr_plural={
        0: '%d archivo',
        1: '%d archivos'
    }
)

# Create entry with metadata
entry = polib.POEntry(
    msgid='Save document',
    msgstr='Guardar documento',
    comment='Tooltip for save button',
    tcomment='This appears in the file menu',
    occurrences=[('views.py', 142), ('templates/base.html', 56)],
    flags=['python-format']
)

# Create fuzzy entry with previous values
entry = polib.POEntry(
    msgid='New welcome message',
    msgstr='Nuevo mensaje de bienvenida',
    flags=['fuzzy'],
    previous_msgid='Welcome message',
    previous_msgstr='Mensaje de bienvenida'
)

Finding Entries

Search for entries within PO/MO files using various criteria including message ID, translation, and context.

class POFile(list):
    def find(self, st, by='msgid', include_obsolete_entries=False, msgctxt=False):
        """
        Find an entry in the PO file.

        Parameters:
        - st (str): String to search for
        - by (str): Field to search in ('msgid', 'msgstr', 'comment', 'tcomment', 'occurrences')
        - include_obsolete_entries (bool): Whether to include obsolete entries in search
        - msgctxt (str or bool): Message context to match, or False to ignore context

        Returns:
        POEntry or None: First matching entry, or None if not found
        """

class MOFile(list):
    def find(self, st, by='msgid', include_obsolete_entries=False, msgctxt=False):
        """Find an entry in the MO file (same interface as POFile.find)."""

Usage Examples:

import polib

po = polib.pofile('messages.po')

# Find by message ID (default)
entry = po.find('Hello World')

# Find by translation
entry = po.find('Hola Mundo', by='msgstr')

# Find by context
entry = po.find('File', msgctxt='menu_item')

# Find in translator comments
entry = po.find('save button', by='tcomment')

# Find including obsolete entries
entry = po.find('Old message', include_obsolete_entries=True)

# Check if entry was found
if entry:
    print(f"Found: {entry.msgid} -> {entry.msgstr}")
else:
    print("Entry not found")

Adding and Inserting Entries

Add new entries to PO/MO files with automatic duplicate checking and proper indexing.

class POFile(list):
    def append(self, entry):
        """
        Add an entry to the end of the PO file.

        Parameters:
        - entry (POEntry): Entry to add

        Returns:
        None

        Raises:
        ValueError: If check_for_duplicates is True and entry already exists
        """

    def insert(self, index, entry):
        """
        Insert an entry at the specified index.

        Parameters:
        - index (int): Position to insert at
        - entry (POEntry): Entry to insert

        Returns:
        None
        """

class MOFile(list):
    def append(self, entry):
        """Add an entry to the MO file (same interface as POFile.append)."""
    
    def insert(self, index, entry):
        """Insert an entry in the MO file (same interface as POFile.insert)."""

Usage Examples:

import polib

po = polib.pofile('messages.po')

# Add a new entry
new_entry = polib.POEntry(
    msgid='New message',
    msgstr='Nuevo mensaje',
    occurrences=[('views.py', 200)]
)
po.append(new_entry)

# Insert entry at specific position
po.insert(0, new_entry)  # Insert at beginning

# Add entry with duplicate checking
po = polib.pofile('messages.po', check_for_duplicates=True)
try:
    po.append(new_entry)
except ValueError as e:
    print(f"Duplicate entry: {e}")

Entry Status and Translation Progress

Check translation status and get statistics about entries in PO/MO files.

class POEntry:
    def translated(self):
        """
        Check if the entry is translated (has non-empty msgstr).

        Returns:
        bool: True if entry is translated, False otherwise
        """

class POFile(list):
    def percent_translated(self):
        """
        Calculate the percentage of translated entries.

        Returns:
        float: Percentage (0-100) of translated non-obsolete entries
        """

    def translated_entries(self):
        """
        Get all translated entries.

        Returns:
        list: List of translated POEntry instances
        """

    def untranslated_entries(self):
        """
        Get all untranslated entries.

        Returns:
        list: List of untranslated POEntry instances
        """

    def fuzzy_entries(self):
        """
        Get all fuzzy entries.

        Returns:
        list: List of fuzzy POEntry instances
        """

    def obsolete_entries(self):
        """
        Get all obsolete entries.

        Returns:
        list: List of obsolete POEntry instances
        """

class MOFile(list):
    def percent_translated(self):
        """Always returns 100.0 (MO files only contain translated entries)."""
    
    def translated_entries(self):
        """Returns self (all entries are translated)."""
    
    def untranslated_entries(self):
        """Returns empty list."""
    
    def fuzzy_entries(self):
        """Returns empty list."""
    
    def obsolete_entries(self):
        """Returns empty list."""

Usage Examples:

import polib

po = polib.pofile('messages.po')

# Check individual entry translation status
for entry in po:
    if not entry.translated():
        print(f"Untranslated: {entry.msgid}")

# Get translation statistics
print(f"Translation progress: {po.percent_translated():.1f}%")
print(f"Translated entries: {len(po.translated_entries())}")
print(f"Untranslated entries: {len(po.untranslated_entries())}")
print(f"Fuzzy entries: {len(po.fuzzy_entries())}")
print(f"Obsolete entries: {len(po.obsolete_entries())}")

# Work with specific entry groups
for entry in po.fuzzy_entries():
    print(f"Fuzzy: {entry.msgid} -> {entry.msgstr}")
    
for entry in po.untranslated_entries():
    entry.msgstr = f"TODO: translate '{entry.msgid}'"

Entry Properties and Flags

Access and modify entry properties including fuzzy status, context, and other metadata.

class POEntry:
    # Properties
    fuzzy: bool            # Property to get/set fuzzy flag
    msgid_with_context: str  # Combined msgid with context for display

    # Attributes
    msgid: str              # Source message ID
    msgstr: str             # Translated message
    msgid_plural: str       # Plural source message
    msgstr_plural: dict     # Plural translations {0: str, 1: str, ...}
    msgctxt: str            # Message context
    comment: str            # Generated comment
    tcomment: str           # Translator comment
    occurrences: list       # List of (filename, line_number) tuples
    flags: list             # List of flags (e.g., ['fuzzy', 'python-format'])
    previous_msgctxt: str   # Previous context (for fuzzy entries)
    previous_msgid: str     # Previous msgid (for fuzzy entries)
    previous_msgid_plural: str  # Previous msgid_plural (for fuzzy entries)
    obsolete: bool          # Whether entry is obsolete
    encoding: str           # Entry encoding
    linenum: int           # Line number in source file

class MOEntry:
    # Core attributes (compatibility attributes are always empty/None)
    msgid: str              # Source message ID
    msgstr: str             # Translated message
    msgid_plural: str       # Plural source message
    msgstr_plural: dict     # Plural translations {0: str, 1: str, ...}
    msgctxt: str            # Message context
    obsolete: bool          # Whether entry is obsolete
    encoding: str           # Entry encoding

Usage Examples:

import polib

po = polib.pofile('messages.po')

# Work with entry properties
for entry in po:
    # Check and modify fuzzy status
    if entry.fuzzy:
        print(f"Fuzzy entry: {entry.msgid}")
        entry.fuzzy = False  # Mark as no longer fuzzy
    
    # Work with context
    if entry.msgctxt:
        print(f"Context '{entry.msgctxt}': {entry.msgid}")
    
    # Display with context
    print(entry.msgid_with_context)
    
    # Access metadata
    if entry.tcomment:
        print(f"Translator note: {entry.tcomment}")
    
    if entry.occurrences:
        for filename, line in entry.occurrences:
            print(f"Found in {filename}:{line}")
    
    # Check flags
    if 'python-format' in entry.flags:
        print(f"Python format string: {entry.msgid}")

# Modify entry attributes
entry = po.find('Hello')
if entry:
    entry.msgstr = 'Hola'
    entry.tcomment = 'Updated translation'
    entry.flags.append('python-format')
    entry.occurrences.append(('new_file.py', 100))

Merging Entries

Merge entries between POEntry instances to combine metadata and translations, or merge POFile with POT template files.

class POEntry:
    def merge(self, other):
        """
        Merge another POEntry into this entry.

        Parameters:
        - other (POEntry): Entry to merge from

        Returns:
        None
        """

class POFile(list):
    def merge(self, refpot):
        """
        Merge the current PO file with a POT file (like gettext msgmerge utility).

        Parameters:  
        - refpot (POFile): Reference POT file to merge from

        Returns:
        None

        Notes:
        - Comments of this file will be preserved
        - Extracted comments and occurrences will be discarded
        - Translations and translator comments will be discarded
        - Dot comments and file positions will be preserved
        - Fuzzy flags are preserved
        """

Usage Examples:

import polib

# Create two entries with different metadata
entry1 = polib.POEntry(
    msgid='Save file',
    msgstr='Guardar archivo',
    occurrences=[('views.py', 100)],
    flags=['python-format']
)

entry2 = polib.POEntry(
    msgid='Save file',
    msgstr='Guardar archivo',
    occurrences=[('forms.py', 50)],
    tcomment='File save action'
)

# Merge entry2 into entry1
entry1.merge(entry2)

# entry1 now contains combined occurrences and translator comment
print(entry1.occurrences)  # [('views.py', 100), ('forms.py', 50)]
print(entry1.tcomment)     # 'File save action'

# Merge POFile with POT template (like msgmerge)
po = polib.pofile('existing_translations.po')  # Existing translations
pot = polib.pofile('messages.pot')             # Updated template

# Merge template into existing translations
po.merge(pot)

# This updates the po file structure based on the pot template:
# - Preserves existing translations where msgids match
# - Adds new entries from the template
# - Marks changed entries as fuzzy
# - Updates source code references
po.save()  # Save the merged result

Install with Tessl CLI

npx tessl i tessl/pypi-polib

docs

entry-manipulation.md

file-operations.md

index.md

utilities.md

tile.json