ProvenPin Install

polib PO catalog read, edit and write roundtrip with MO compile: working versions (tested 2026-10-16)

Read, edit and write gettext PO catalogs with polib, compile them to MO, and audit fuzzy and untranslated entries fully offline.

passed6 passed · 0 failed2026-10-16 21:57 UTC
Status
passed
Last passed
2026-10-16 21:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
polib@1.2.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38972656245

The code

Exactly the files that passed. Your agent can pull them with get_recipe("polib-po-catalog-roundtrip") (install), or see them on GitHub.

requirements.txt
polib==1.2.0
pytest==9.1.1
catalog.py
import polib

PLURAL_FORMS = "nplurals=2; plural=(n > 1);"


def new_catalog(locale="fr"):
    po = polib.POFile()
    po.metadata = {
        "Project-Id-Version": "demo",
        "Language": locale,
        "Content-Type": "text/plain; charset=UTF-8",
        "Plural-Forms": PLURAL_FORMS,
    }
    return po


def add(po, msgid, msgstr="", fuzzy=False, plural=None):
    # fuzzy=True is silently dropped by POEntry; the flag list is the real switch
    entry = polib.POEntry(
        msgid=msgid,
        msgstr=msgstr,
        msgid_plural=plural,
        msgstr_plural={},
        flags=["fuzzy"] if fuzzy else [],
    )
    po.append(entry)
    return entry


def translate(po, msgid, msgstr):
    entry = po.find(msgid)
    if entry is None:
        entry = add(po, msgid)
    entry.msgstr = msgstr
    if "fuzzy" in entry.flags:
        entry.flags.remove("fuzzy")
    return entry


def load(path):
    return polib.pofile(str(path))


def save(po, path):
    po.save(str(path))


def compile_to_mo(po, path):
    po.save_as_mofile(str(path))


def missing_forms(entry, nplurals=2):
    return [i for i in range(nplurals) if entry.msgstr_plural.get(i, "") == ""]


def audit(po):
    return {
        "total": len(po),
        "translated": len(po.translated_entries()),
        "fuzzy": len(po.fuzzy_entries()),
        "untranslated": len(po.untranslated_entries()),
        "percent": po.percent_translated(),
    }
tests/test_catalog.py
import polib

from catalog import add, audit, compile_to_mo, load, missing_forms, new_catalog, save, translate


def test_edit_roundtrip_upserts_and_persists(tmp_path):
    po = new_catalog("fr")
    add(po, "Hello", "Bonjour")
    add(po, "Bye", "")
    path = tmp_path / "fr.po"
    save(po, path)

    reloaded = load(path)
    translate(reloaded, "Bye", "Au revoir")
    translate(reloaded, "New", "Nouveau")
    save(reloaded, path)

    again = load(path)
    assert again.find("Hello").msgstr == "Bonjour"
    assert again.find("Bye").msgstr == "Au revoir"
    assert again.find("New").msgstr == "Nouveau"
    assert audit(again)["untranslated"] == 0


def test_fuzzy_kwarg_is_ignored_only_flags_work():
    ignored = polib.POEntry(msgid="x", msgstr="y", fuzzy=True)
    assert ignored.fuzzy is False
    assert ignored.translated() is True

    marked = polib.POEntry(msgid="x", msgstr="y", flags=["fuzzy"])
    assert marked.fuzzy is True
    assert marked.translated() is False


def test_fuzzy_is_neither_translated_nor_untranslated(tmp_path):
    po = new_catalog("fr")
    add(po, "Draft", "Brouillon", fuzzy=True)
    add(po, "Done", "Fait")
    add(po, "Todo", "")
    path = tmp_path / "fr.po"
    save(po, path)

    reloaded = load(path)
    assert [e.msgid for e in reloaded.translated_entries()] == ["Done"]
    assert [e.msgid for e in reloaded.fuzzy_entries()] == ["Draft"]
    assert [e.msgid for e in reloaded.untranslated_entries()] == ["Todo"]
    assert audit(reloaded)["percent"] == 33


def test_translate_clears_fuzzy_flag():
    po = new_catalog("fr")
    add(po, "Greeting", "Salut", fuzzy=True)
    translate(po, "Greeting", "Bonjour")
    entry = po.find("Greeting")
    assert entry.fuzzy is False
    assert entry.translated() is True


def test_partial_plural_missing_form_is_not_flagged(tmp_path):
    po = new_catalog("fr")
    po.append(polib.POEntry(msgid="File", msgid_plural="Files", msgstr_plural={0: "Fichier"}))
    po.append(polib.POEntry(msgid="Cat", msgid_plural="Cats", msgstr_plural={0: "Chat", 1: ""}))
    path = tmp_path / "fr.po"
    save(po, path)

    reloaded = load(path)
    assert reloaded.find("File").translated() is True
    assert missing_forms(reloaded.find("File"), nplurals=2) == [1]
    assert reloaded.find("Cat").translated() is False
    assert missing_forms(reloaded.find("Cat"), nplurals=2) == [1]


def test_mo_compile_drops_fuzzy_and_untranslated(tmp_path):
    po = new_catalog("fr")
    add(po, "Hello", "Bonjour")
    add(po, "Draft", "Brouillon", fuzzy=True)
    add(po, "Todo", "")
    add(po, "File", plural="Files")
    po.find("File").msgstr_plural = {0: "Fichier", 1: "Fichiers"}

    mo_path = tmp_path / "fr.mo"
    compile_to_mo(po, mo_path)

    mo = polib.mofile(str(mo_path))
    assert mo.find("Hello").msgstr == "Bonjour"
    assert mo.find("File").msgstr_plural == {0: "Fichier", 1: "Fichiers"}
    assert mo.find("Draft") is None
    assert mo.find("Todo") is None
    assert mo.metadata["Content-Type"] == "text/plain; charset=UTF-8"
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.