{"slug":"po-translate","name":"PO Translate: Gettext Files That Still Compile","version":"1.0.0","updated_at":"2026-10-08T15:13:39.419Z","use_when":"Translates the messages of a gettext PO file (.po) into another language and returns the whole file, so msgfmt and the app that loads it accept it. Only the msgstr values change. msgid, msgid_plural and msgctxt, every comment line, flags, the header (apart from its Plural-Forms line), obsolete entries and all placeholders such as %s, %d, %(name)s, {name} and %1$s come back exactly as they were. Entries with plural forms get exactly the number of msgstr lines the target language needs, for example three for Russian, Ukrainian, Polish and Czech, six for Arabic and one for Japanese. Use to translate or localize a .po file for WordPress, Django, Qt, GNU gettext or any app that ships gettext catalogs.","not_for":"Choosing terminology for you, translating a .pot template into a language the request does not name, merging a translated file with a newer template (msgmerge does that), compiling .mo files, or translating other formats such as JSON or XLIFF. It translates one .po file per request and does not rename, split or merge files.","languages":["any"],"tags":["translation","gettext","po-file","localization","plural-forms","i18n"],"category":"translation","category_url":"https://aiskills402.com/categories/translation","keywords":["localize a .po file","gettext PO file","plural forms"],"faq":[{"q":"What does it change in a .po file?","a":"Only the msgstr values, plus the Plural-Forms line of the header when it is missing or belongs to another language. msgid, msgid_plural, msgctxt, every comment line, flags, the other header lines and obsolete entries come back exactly as they were, so msgmerge still matches your strings. It also keeps the blank lines, the order of entries and the long multi-line strings meaning the same, so a diff against your old file shows translated text and nothing else. Flags stay too: a fuzzy entry stays fuzzy, for a human to approve."},{"q":"How many plural lines does an entry get?","a":"As many as the target language has plural forms: two for German or Spanish, three for Russian, Ukrainian, Polish and Czech, six for Arabic, one for Japanese. The skill carries the gettext formulas, writes the header line when it is missing, and replaces an English one copied from a template. Arabic gets separate sentences for zero, one and two; Russian form zero keeps the number because it also serves twenty-one. Wrong counts make a catalog load with warnings or show the wrong noun form, which is why every plural entry is checked against the header."},{"q":"What about %s, %(name)s and a changed word order?","a":"Every placeholder stays spelled as in the source. When the target language needs the arguments in another order, all of them are numbered (%2$s before %1$d), because plain %s in a new order formats the wrong value. Named placeholders such as %(count)d need no numbers. Markup inside a string, such as a link with a %s address, stays too. Escapes survive as well: a quote inside a string stays backslash-quote, so Python, PHP, Qt and C loaders still parse the file. Numbered forms follow the manual, which msgfmt understands."},{"q":"Is there a measured gain for Claude Sonnet?","a":"Modest. We ran 24 test files on Sonnet and Haiku and scored the content. Without the skill Sonnet got 22 right and Haiku 19; with it, 24 and 23. Sonnet already knew most plural counts. Its gaps were an obsolete entry it filled in although it had to stay untouched, and a number placeholder it dropped in Arabic that a strict check counts. Haiku gained most but reordered plain placeholders once. Both fenced the bare answers."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: Tallybook 4.2\\n\"\n\"Report-Msgid-Bugs-To: i18n@tallybook.example\\n\"\n\"POT-Creation-Date: 2026-09-30 10:12+0000\\n\"\n\"PO-Revision-Date: 2026-10-01 09:00+0000\\n\"\n\"Last-Translator: Mara Lind <mara@tallybook.example>\\n\"\n\"Language-Team: Tallybook translators <i18n@tallybook.example>\\n\"\n\"Language: ru\\n\"\n\"MIME-Version: 1.0\\n\"…","output_excerpt":"msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: Tallybook 4.2\\n\"\n\"Report-Msgid-Bugs-To: i18n@tallybook.example\\n\"\n\"POT-Creation-Date: 2026-09-30 10:12+0000\\n\"\n\"PO-Revision-Date: 2026-10-01 09:00+0000\\n\"\n\"Last-Translator: Mara Lind <mara@tallybook.example>\\n\"\n\"Language-Team: Tallybook translators <i18n@tallybook.example>\\n\"\n\"Language: ru\\n\"\n\"MIME-Version: 1.0\\n\"…"}],"page_url":"https://aiskills402.com/skills/po-translate","markdown_url":"https://aiskills402.com/skills/po-translate.md","image_url":"https://cdn.aiskills402.com/og/skills/po-translate/77e7fa96.png","related_url":"https://api.aiskills402.com/v1/skills/po-translate/related","purchases_count":null,"tested":{"date":"2026-10-08","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right in all 24 files, checked by the structure of the .po file: every msgid, msgctxt and comment line came back unchanged, Russian, Ukrainian, Polish and Czech entries got three msgstr lines, Arabic six and Japanese one, a missing Plural-Forms line was added and an English one replaced, Turkish and Japanese word order used numbered %1$s placeholders, and the obsolete #~ entries, including an empty msgstr[2], were left as they were."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right in 23 of 24 files, with the same plural counts and header fixes as Sonnet. It missed one: in a Turkish file it put the arguments of \"Sent %d reports to %s\" in the other order with plain %s and %d, which formats the wrong value, and it wrapped that file in a code fence although the skill says not to."},"note":"Twenty-four .po files written by us for a fictional app, translated into Russian, Ukrainian, Polish, Czech, Arabic, Japanese, Turkish, Bulgarian, German, Spanish and Italian. They hold plural entries with 1 to 6 forms, headers with the right, a wrong (English) or no Plural-Forms line, msgctxt pairs, developer and translator comments, source references, c-format, python-format and brace placeholders, numbered %1$s placeholders, multi-line and escaped strings, an entry left fuzzy, symbols with no words, and obsolete #~ entries. The check parses the answer as a .po file: entries and msgctxt/msgid/msgid_plural identical and in order, #. and #: lines and #~ lines unchanged, the header kept except Plural-Forms, the msgstr count equal to the language, every msgstr filled and different from the source, placeholders the same, numbered when the order changes. The bare models wrapped almost every file in a code fence with notes around it, so the numbers are scored by content (the first code block), not by the fence. One run per model and file.","baseline":{"date":"2026-10-08","rows":[{"label":"Valid .po file by content (24 files)","better":"higher","strong":{"with":{"n":24,"of":24},"without":{"n":22,"of":24}},"weak":{"with":{"n":23,"of":24},"without":{"n":19,"of":24}}}],"note":"Same request on both sides, scored by the content of the first code block because the bare answers were fenced. Read by hand, Sonnet without the skill had two misses. It filled the empty msgstr[2] of an obsolete #~ entry and said so in a note, which changes a block that must stay untouched. In the Arabic file it dropped %d from the zero, one and two forms; the gettext manual allows that for a form that serves one number, so this one is a strict check, not a clear error. Haiku without the skill kept an English two-form header or none and wrote two msgstr lines for Russian, Polish, Japanese and Arabic, and filled the obsolete entry too."},"report_url":null},"price_usd":"0.02","price_micro":20000,"size_bytes":9441,"sha256":"8bae4954bd48390b1c03e1346e0924e109b5e9654d5625b7cd4bef5aa0441709","outline":["The answer","What you translate","What you keep exactly","Plural forms","Placeholders","Strings","Words","Work in this order","Short example"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/po-translate/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"Translate .po Files Without Breaking Gettext","seo_description":"A SKILL.md that translates gettext .po files: msgid, comments and placeholders untouched, the right number of plural forms per language. $0.02, yours forever.","versions":[{"version":"1.0.0","date":"2026-10-08","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-08\n\nFirst release: translates a gettext .po file and returns the whole file with only the msgstr values changed. msgid, msgid_plural, msgctxt, comment lines, flags, obsolete entries, the header apart from its Plural-Forms line, and all placeholders stay as written. Plural entries get the number of msgstr lines the target language needs (the gettext formulas are in the skill), and placeholders are numbered when the word order changes. Price $0.02. Measured value, by content (the bare models fenced nearly every file): Sonnet 22 of 24 files right without the skill, 24 of 24 with it; Haiku 19 of 24 without, 23 with. One clear content gain for Sonnet (the obsolete entry); the Arabic case is a strict check, so the price is $0.02.\n"}]}