How to Translate PO Files Without Breaking Your App
TABLE OF CONTENTS
PO files look like simple text files until a translated %s, missing plural form, or edited msgid breaks your app. Use this workflow to translate the human-facing strings while leaving the gettext structure intact.
Do not paste the whole file into a normal text translator. A PO file is source code adjacent: the words are translatable, but the file structure, placeholders, comments, and plural indexes have to survive unchanged.
Method 1: Use a PO File Translator
Pick this when you want the fastest safe first draft and do not want to edit msgid / msgstr pairs by hand.
-
Back up the original
.pofile. Keep a clean copy in your repository before sending anything to a translator. If the translated file breaks, you need a known-good version to compare against. -
Open a PO-aware translator. Use a tool built for gettext files, such as OpenL PO Translator, a pay-per-use document translation tool. A PO-aware tool should translate the target strings while keeping source strings, comments, placeholders, and file structure intact. If you are still choosing tools, compare options in our best PO translator guide.
-
Upload the
.pofile. Use the language file that containsmsgidandmsgstrentries. If you only have a.pottemplate, create a target-language.pofile from it first, then upload the.pofile. -
Choose the source and target language. Match the source language to the text inside
msgid, not the language of your admin interface. For example, if the file contains Englishmsgidstrings and you need Spanish output, choose English to Spanish. -
Download the translated file. Save it with the locale naming convention your framework expects. WordPress plugins often use a text-domain plus locale pattern, while Django usually stores files under
locale/<language>/LC_MESSAGES/. -
Review the risky strings first. Search the translated file for
%,{,},<,>,msgid_plural,msgctxt, and#, fuzzy. These are the entries most likely to affect runtime behavior. -
Test the translated file in your app. Load the language locally and click through the screens that use the translated strings. A PO file is not done when it is translated; it is done when the app still renders correctly.
Method 2: Translate PO Files in Poedit
Pick this when you need human review, WordPress compatibility, or a careful entry-by-entry workflow.
-
Open the file in Poedit. Poedit is a dedicated translation editor for PO and other localization formats; the basic editor is free, with paid Pro features for heavier workflows. For WordPress, the official Polyglots handbook explains that Poedit can create
.poand.mofiles from a POT file and supports plural forms and UTF-8. -
Update from the POT template if the source changed. If developers changed the app text, update the
.pofile from the latest.potbefore translating. This keeps new, removed, and fuzzy strings visible instead of silently shipping outdated UI text. -
Translate the
msgstrfield only. Themsgidis the source string your app uses to look up the translation. In normal gettext workflows, translators should editmsgstr, notmsgid. -
Keep placeholders exactly intact. Do not translate or re-space variables such as
%s,%d,%1$s,{name},%(count)s,:name, or HTML tags. If the word order must change, move the placeholder as a unit. -
Handle plural forms as separate translations. A plural entry can contain
msgid,msgid_plural, and multiplemsgstr[n]values. Fill every plural slot required by the target language instead of copying the same sentence everywhere. -
Save and compile the
.mofile if your app needs it. Some stacks read.pofiles directly during development, but WordPress and many gettext setups use compiled.mofiles at runtime. Poedit can compile.moon save; Django can compile messages withdjango-admin compilemessages. -
Resolve warnings before upload. In Poedit, warning icons often point to broken placeholders, missing variables, or plural mismatches. Fix those before importing the translation into WordPress, Django, Drupal, or your release branch.
Method 3: Use a Localization Platform
Pick this when several translators, reviewers, or release managers need to work on the same PO files.
-
Import the PO file into a platform that supports gettext. Weblate and similar localization platforms support PO workflows. Team localization platforms are often paid products, although Weblate also has an open-source self-hosted option. Their handling of comments, headers, fuzzy strings, and placeholders differs, so check the format settings before uploading production files.
-
Set placeholder and tag checks. Turn on QA rules for printf-style placeholders, named variables, HTML/XML tags, and plural forms. These checks catch the mistakes that normal spellcheckers cannot see.
-
Keep developer comments visible. PO comments can carry context such as source references, extracted developer notes, flags, and previous source strings. Translators need those notes when a short UI label like “Open” could be a verb, adjective, or menu command.
-
Use translation memory with review. Translation memory is useful for repeated UI strings, but it can copy an old translation into a new context. Review reused strings when
msgctxt, source references, or surrounding UI changed. -
Export PO files and run local checks. Do not trust the export blindly. Put the translated file back into the app, compile if needed, and test the screens before merging.
PO File Rules You Should Never Break
| PO item | Translate it? | Safe example | Why it matters |
|---|---|---|---|
msgid | No | msgid "Save changes" | The app uses this source string as the lookup key in many gettext workflows. |
msgstr | Yes | msgstr "Guardar cambios" | This is the target-language text users see. |
msgctxt | No | msgctxt "button" | Context disambiguates identical source strings. |
%s, %d, %1$s | No | Hello, %s -> Hola, %s | Runtime code replaces these placeholders with live values. |
{name}, %(count)s, :name | No | Welcome, {name} | Named variables must still match the app code. |
| HTML tags | Usually no | <strong>Warning</strong> | Translate the text, not the tag syntax. |
msgid_plural | No | msgid_plural "%d files" | The source plural belongs to the original code path. |
msgstr[0], msgstr[1] | Yes, carefully | msgstr[0] "%d file" | Each target language has its own plural rules. |
#, fuzzy | Review first | #, fuzzy | Fuzzy means the translation may be outdated or unconfirmed. |
#. developer comments | Usually no | #. Button label | These notes help translators understand context. |
Quick Example: Safe vs. Broken PO Translation
Here is a normal gettext entry:
#. %s is the user's display name.
#, c-format
msgid "Welcome back, %s"
msgstr ""
A safe Spanish translation keeps %s unchanged:
#. %s is the user's display name.
#, c-format
msgid "Welcome back, %s"
msgstr "Bienvenido de nuevo, %s"
A broken translation changes the placeholder:
msgid "Welcome back, %s"
msgstr "Bienvenido de nuevo, % s"
That tiny space can matter. GNU msgfmt --check-format is designed to catch format-string mismatches such as wrong % placeholders, and Poedit also warns about common placeholder problems. For a broader list of strings that should stay untouched, use our guide to what not to translate.
How to Check a Translated PO File
- Run gettext validation if you have gettext installed.
msgfmt --check --check-format -o /tmp/messages.mo path/to/messages.po
This checks syntax, headers, and format strings, then writes a temporary compiled catalog if the file is valid.
- Compile the file the way your framework expects.
django-admin compilemessages
For Django projects, compilemessages compiles .po files created by makemessages into .mo files for gettext support.
- Search for empty translations.
grep -n 'msgstr ""' path/to/messages.po
Empty msgstr fields may be intentional for untranslated entries, but they should not surprise you during release.
- Search for fuzzy strings.
grep -n '#, fuzzy' path/to/messages.po
Fuzzy strings should be reviewed by a person before release. By default, msgfmt does not use fuzzy translations unless you compile with --use-fuzzy, so a fuzzy entry can behave like an untranslated string in the final catalog.
- Test the real UI. Open the screens that contain forms, plural counts, error messages, account menus, and payment flows. PO validation catches file problems; only UI testing catches awkward wording, overflow, and missing context.
Which Method Should You Use?
| Situation | Best method | Why |
|---|---|---|
| You need a quick first draft for one PO file | PO file translator | Fastest path with structure preservation |
| You maintain a WordPress plugin or theme | Poedit | Familiar WordPress workflow with .mo compilation |
| You maintain a Django app | PO translator or Poedit, then compilemessages | Translation can be quick, but framework compilation is still required |
| You have many languages and reviewers | Localization platform | Better assignment, history, QA, and review controls |
| You are translating developer-facing strings | Human review after machine translation | Code terms, placeholders, and context matter more |
| You are localizing JSON or frontend i18n files too | Use a format-specific workflow | PO rules do not always apply to JSON, YAML, or ICU messages |
If your project mixes gettext PO files with JSON locale files, translate each format with a tool that understands its structure. PO files revolve around msgid and msgstr; JSON localization revolves around keys and values. For that workflow, see our guide to the best JSON translators in 2026.
FAQ
Can I translate PO files with Google Translate?
You can copy individual msgstr values into a general translator, but uploading or pasting the whole PO file into a plain text translator is risky. General translators may alter msgid, comments, quote escaping, plural indexes, or placeholders. Use a PO-aware translator, Poedit, or a localization platform instead.
What is the difference between .po, .pot, and .mo?
.pot is the template extracted from source code. It usually contains original strings but no completed translations. .po is the editable translation file for one target language. .mo is the compiled binary catalog that many gettext-based apps load at runtime.
Should I translate msgid?
No, not in the normal workflow. Translate msgstr. The GNU gettext manual describes msgid as the original untranslated string and msgstr as the translation; msgid strings are produced and managed by gettext tools.
How do I check if a PO file is valid?
Run msgfmt --check --check-format if gettext is available, open the file in Poedit, or use your localization platform’s QA checks. Then compile and test the file inside the app. Validation is necessary, but it is not a replacement for UI testing.
What happens if I break a placeholder?
Best case, the app shows a strange string. Worse case, the runtime formatter throws an error because the translated string no longer matches the variables the code passes in. Placeholders should be moved only as complete tokens, never translated or partially edited.
Can OpenL translate PO files?
Yes. OpenL PO Translator is built for gettext .po files and says it keeps placeholders and variables untouched while translating across 100+ languages. It uses a pay-per-use document translation workflow, so use it for a fast first draft when preserving PO structure matters more than doing everything manually. If you only have a .pot template, create a target-language .po file before using OpenL.
Sources
- GNU gettext manual: PO Files — Official gettext explanation of the PO file format.
- GNU gettext manual: PO File Entries — Source for
msgid,msgstr, comments, flags, and entry structure. - GNU gettext manual: Entries with Plural Forms — Source for plural-form entry structure.
- GNU gettext manual: msgfmt Invocation — Source for
msgfmt --checkand format-string validation. - OpenL PO Translator — OpenL product page for translating
.pofiles while preserving placeholders and variables. - Poedit — Official Poedit site for the PO translation editor and Pro offering.
- WordPress Polyglots Handbook: Poedit — WordPress guidance on using Poedit, POT files, PO files, MO compilation, and placeholder warnings.
- Django documentation: Translation — Django workflow for message files and translation.
- Django documentation: compilemessages — Django command reference for compiling
.pofiles into.mofiles. - Drupal documentation: PO and POT files — Drupal explanation of
.po,.pot, context, plural forms, comments, and variables. - Weblate documentation: GNU gettext PO — Localization-platform notes on PO headers, previous source strings, obsolete strings, and generated MO files.