# PO File Repair: Make msgfmt Accept It Again

PO File Repair: Make msgfmt Accept It Again is a tested SKILL.md that repairs a gettext .po translation file so that msgfmt --check compiles it, changing only what blocks compilation and translating nothing; an agent buys it once for $0.05 over x402.

- Page: https://aiskills402.com/skills/po-file-repair
- Category: Translation & Localization (https://aiskills402.com/categories/translation)
- Price: $0.05 once, USD-priced, paid in USDC on Base over x402. Price as loaded on this page. The 402 response your agent receives is authoritative.
- Version: 1.0.0
- Card (JSON): https://api.aiskills402.com/v1/skills/po-file-repair

## Use it when

Repairs a gettext .po translation file so that msgfmt --check compiles it, changing only what blocks compilation and translating nothing. Knows what the checker really rejects - an unescaped quote or a missing closing quote, an escape it does not know, a msgid that starts or ends with a newline while its msgstr does not, c-format or python-format placeholders that differ, the wrong number of plural forms, msgstr without an index on a plural entry or with one on a plain entry, charset=CHARSET left in the header, plural entries with no Plural-Forms, and the same entry twice - and what it accepts, so a working file is left alone. A translation that cannot be fixed without new words keeps its text and gets the fuzzy flag; two different translations of one entry, or a plural formula the file gives no way to know, get a fixed one-line CANNOT REPAIR answer. Use when msgfmt, a build, Django compilemessages, Poedit or a CI step fails on a .po file, or when asked to fix a broken PO or gettext catalog.

## Not for

Translating empty entries, reviewing translation quality, merging a new template into an old catalog, or files other than gettext .po (XLIFF, Android, JSON). It fixes what stops compilation in one file and leaves every word, comment and entry order as it found them.

## Tested, honestly

Tested 2026-10-09.

- Strong model (claude-sonnet-5-5 (Claude Code alias "sonnet")): Right on all 24 files, compared by meaning with an answer msgfmt --check accepts: quotes escaped and closed, an unknown backslash doubled, newlines matched at the right end, a wrong placeholder type replaced, fuzzy added where a placeholder or a plural form was missing, msgstr indexes put right, charset and Plural-Forms fixed from the Language header, an identical duplicate dropped, comments, flags and #~ lines kept, empty entries left untranslated despite a comment asking otherwise, four files that already compile left alone, and a CANNOT REPAIR line for two different translations of one entry and for a plural formula with no language named.
- Weak model (claude-haiku-5-5 (Claude Code alias "haiku")): Right on 20 of 24, but twice it dropped the header entry while repairing, once added a newline at the wrong end of a translation, and refused one file in a plain sentence instead of the CANNOT REPAIR line.

Note: Twenty-four .po files written by us in Bulgarian, Russian, Ukrainian and German: 18 broken in a way msgfmt --check rejects (quotes, escapes, newlines, c-format and python-format placeholders, plural forms, msgstr indexes, charset, Plural-Forms, a duplicate, several faults at once, a comment asking to translate, a fuzzy entry and obsolete lines to keep), 2 that must be refused and 4 that already compile. The case script runs msgfmt --check (GNU gettext) on every input, ideal and control, and refuses to write the cases unless each broken input is rejected and each ideal and control accepted; it caught one of our own ideals (an empty plural form added to a c-format entry is rejected). Each answer is compared with the ideal by meaning: entries, translations after unescaping, flags, comments and obsolete lines. The shared request states what may change, when to add fuzzy and when to refuse. No check was widened. One run per model and file.

### With and without the skill

Tested 2026-10-09.

- Files repaired right (24 files): Sonnet 24/24 with, 20/24 without; Haiku 21/24 with, 16/24 without.

Same request and same checks on both sides; on the two refusals both sides are scored on any clear sentence. Read by hand, Sonnet without the skill missed four, all on what msgfmt checks: it added an empty third plural form to a c-format entry (msgfmt rejects it; the request said to add fuzzy when words are missing), moved a translation into the second form instead of indexing it, refused a Ukrainian file whose header only needed the standard Ukrainian Plural-Forms, and changed a placeholder on an entry with no c-format flag, which already compiled. Haiku without the skill missed eight.

Same cases and the same checks with and without the skill. The cases are ours, written around what the skill is for; with a handful of cases, a difference of one or two is within noise.

Full summary: https://aiskills402.com/skills/po-file-repair/tests

## Example

### Bulgarian (claude-sonnet-5-5)

Before:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: bg\n" "MIME-Version: 1.0\n"…

After:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: bg\n" "MIME-Version: 1.0\n"…

### German (claude-sonnet-5-5)

Before:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: de\n" "MIME-Version: 1.0\n"…

After:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: de\n" "MIME-Version: 1.0\n"…

### Russian (claude-sonnet-5-5)

Before:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: ru\n" "MIME-Version: 1.0\n"…

After:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: ru\n" "MIME-Version: 1.0\n"…

### uk (claude-sonnet-5-5)

Before:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: uk\n" "MIME-Version: 1.0\n"…

After:

> msgid "" msgstr "" "Project-Id-Version: Tallybook 4.2\n" "Report-Msgid-Bugs-To: i18n@tallybook.example\n" "POT-Creation-Date: 2026-09-30 10:12+0000\n" "PO-Revision-Date: 2026-10-01 09:00+0000\n" "Last-Translator: Mara Lind <mara@tallybook.example>\n" "Language-Team: Tallybook translators <i18n@tallybook.example>\n" "Language: uk\n" "MIME-Version: 1.0\n"…

## What is in the file

- The answer
- What the checker rejects, and the fix
- What the checker accepts — leave it
- The fuzzy flag
- When to refuse
- Rules
- Work in this order
- Short example

## Languages

Any language

## How to buy

Agent (HTTP):

1. GET https://api.aiskills402.com/v1/skills/po-file-repair/file without a payment header. The answer is 402 with a PAYMENT-REQUIRED header (x402 v2): exact amount, asset, network, recipient.
2. Sign `accepts[0]` with an x402 client (for example @x402/core + @x402/evm).
3. Repeat the GET with the signature in the PAYMENT-SIGNATURE header. The answer is 200 with the file, its sha256 and a re-download token.

Agent (MCP): https://mcp.aiskills402.com/mcp — free tools search_skills, get_skill, redownload_skill. Buying itself is over HTTP.

Full flow: https://aiskills402.com/docs

## The file

- Version: 1.0.0
- Size: 8.0 KB (8239 bytes)
- SHA-256: 8d5020bfa3e24856a675ff17433d092c7175ef0c66cb371cb08d781c6b9c3c32
- Updated: 2026-10-09
- New versions are free through your re-download token.

## Versions

### 1.0.0 (2026-10-09)

First release: repairs a gettext .po file so that msgfmt --check compiles it, with the smallest edit per fault and nothing translated. The faults it fixes and the cases it leaves alone were measured with msgfmt --check; a translation that only new words could fix keeps its text and gets the fuzzy flag; two different translations of one entry, or a plural formula with no Language to take it from, get one CANNOT REPAIR line. Twenty-four test cases; the case script proves with msgfmt that every broken input is rejected and every ideal and control is accepted, and the tester compares answers with the ideal by meaning. No model run yet: the baseline, the final price and the sentence on what Sonnet gains are still to be written. Price 20000 is provisional.

## License

Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing. Holder: Georgi Kalchev, aiskills402.com. Terms: https://aiskills402.com/docs#license

## FAQ

### Which faults does it fix?

The ones msgfmt --check actually rejects, as measured: an unescaped or missing quote, an unknown backslash escape, a newline at one end of msgid only, placeholders that differ on a c-format or python-format entry, the wrong number of plural forms, msgstr with or without an index in the wrong place, charset=CHARSET left in the header, a missing Plural-Forms line, and the same entry twice with the same text.

### What if a translation is broken in a way only new words can fix?

It keeps the text and adds the fuzzy flag to that entry, so the compiler skips it, the build passes and a translator sees what to revisit. It never empties a translation to make the error go away, never writes a missing plural form itself and never translates an empty entry, even when a comment in the file asks for it.

### When does it refuse?

When the same entry appears twice with two different translations, because keeping one is a decision for a person, and when plural entries need a Plural-Forms line but the file names no language to take the formula from. Either way you get a single CANNOT REPAIR line with the reason instead of a file that hides the problem.

### Does it help Claude Sonnet?

Yes, by four files of twenty-four, which puts it at five cents. Both Claude models repaired the same broken catalogs with this file and without it, and each answer was compared with a fix that msgfmt --check accepts. Unaided, Sonnet added an empty plural form that the checker still rejects, shifted a translation into the wrong form, refused a Ukrainian header it could have fixed, and changed a placeholder on an entry that already compiled. Guided by the file, it repaired every one. Haiku went from 16 to 21.

## Related skills

- [Translate .po Files Without Breaking Gettext](https://aiskills402.com/skills/po-translate.md): $0.02 once
- [Translate App Text (JSON, YAML, PO)](https://aiskills402.com/skills/translate-app-text.md): $0.01 once
- [XLIFF Translate: .xlf Files Your Tool Imports Back](https://aiskills402.com/skills/xliff-translate.md): $0.01 once

## Measurement limits

- Models other than the two named above were not run.
- Each verdict comes from the test run on the date shown; the skill may have changed since (check the version).
- Full test inputs are not published here, only short excerpts of our own text.
- Results on your own texts, languages and domains can differ.

Offer note: Paid in USDC (USD-pegged) over x402 by an AI agent; one-time.
