How to Translate PO Files Without Breaking Your App

OpenL Team 7/3/2026
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.

  1. Back up the original .po file. 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.

  2. 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.

  3. Upload the .po file. Use the language file that contains msgid and msgstr entries. If you only have a .pot template, create a target-language .po file from it first, then upload the .po file.

  4. 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 English msgid strings and you need Spanish output, choose English to Spanish.

  5. 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/.

  6. 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.

  7. 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.

  1. 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 .po and .mo files from a POT file and supports plural forms and UTF-8.

  2. Update from the POT template if the source changed. If developers changed the app text, update the .po file from the latest .pot before translating. This keeps new, removed, and fuzzy strings visible instead of silently shipping outdated UI text.

  3. Translate the msgstr field only. The msgid is the source string your app uses to look up the translation. In normal gettext workflows, translators should edit msgstr, not msgid.

  4. 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.

  5. Handle plural forms as separate translations. A plural entry can contain msgid, msgid_plural, and multiple msgstr[n] values. Fill every plural slot required by the target language instead of copying the same sentence everywhere.

  6. Save and compile the .mo file if your app needs it. Some stacks read .po files directly during development, but WordPress and many gettext setups use compiled .mo files at runtime. Poedit can compile .mo on save; Django can compile messages with django-admin compilemessages.

  7. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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 itemTranslate it?Safe exampleWhy it matters
msgidNomsgid "Save changes"The app uses this source string as the lookup key in many gettext workflows.
msgstrYesmsgstr "Guardar cambios"This is the target-language text users see.
msgctxtNomsgctxt "button"Context disambiguates identical source strings.
%s, %d, %1$sNoHello, %s -> Hola, %sRuntime code replaces these placeholders with live values.
{name}, %(count)s, :nameNoWelcome, {name}Named variables must still match the app code.
HTML tagsUsually no<strong>Warning</strong>Translate the text, not the tag syntax.
msgid_pluralNomsgid_plural "%d files"The source plural belongs to the original code path.
msgstr[0], msgstr[1]Yes, carefullymsgstr[0] "%d file"Each target language has its own plural rules.
#, fuzzyReview first#, fuzzyFuzzy means the translation may be outdated or unconfirmed.
#. developer commentsUsually no#. Button labelThese 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

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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?

SituationBest methodWhy
You need a quick first draft for one PO filePO file translatorFastest path with structure preservation
You maintain a WordPress plugin or themePoeditFamiliar WordPress workflow with .mo compilation
You maintain a Django appPO translator or Poedit, then compilemessagesTranslation can be quick, but framework compilation is still required
You have many languages and reviewersLocalization platformBetter assignment, history, QA, and review controls
You are translating developer-facing stringsHuman review after machine translationCode terms, placeholders, and context matter more
You are localizing JSON or frontend i18n files tooUse a format-specific workflowPO 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