Skip to content

GNU gettext (.po, .pot, .mo)

Format, editor, and workflow for the most widely used open-source translation format.

GNU gettext is the most widely used translation format in open-source software. A gettext project has three file types: a .pot template (source strings only), one .po per language (source + translations), and a .mo compiled binary that the gettext runtime loads. The .po format is plain text — pairs of msgid (source) and msgstr (translation), with metadata in the file header, source-line comments, and full plural-form support. Used by WordPress, Django, Drupal, GNOME, KDE, MediaWiki, and most C/C++ GNU applications, gettext is effectively the de-facto translation format in the open-source world.

Key facts
  • What it is: text-based translation format with template, translation, and compiled forms
  • Origin: GNU project (1995, Ulrich Drepper)
  • File extensions: .po, .pot (template), .mo (compiled)
  • Encoding: UTF-8 (declared in header)
  • Plurals: per-language Plural-Forms header (CLDR-derived)
  • Used by: WordPress, Django, Flask, Drupal, GNOME, KDE, MediaWiki, Rails (gettext gem), most GNU C/C++ apps

What a .po file looks like

A .po file starts with a header (an empty msgid with metadata in the msgstr) followed by entries. Each entry is one source message with optional context, plural forms, source-line references, and translator/extracted comments:

msgid ""
msgstr ""
"Project-Id-Version: My Project 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: de\n"

#: src/login.js:12
msgid "Sign in"
msgstr "Anmelden"

#: src/cart.js:42
msgid "%d item in your cart"
msgid_plural "%d items in your cart"
msgstr[0] "%d Artikel in Ihrem Warenkorb"
msgstr[1] "%d Artikel in Ihrem Warenkorb"

# Reviewed by translator on 2026-04-12
msgctxt "menu"
msgid "File"
msgstr "Datei"

The Plural-Forms header declares how many plural forms the language uses and the rule that picks them — German and English use 2 forms, Polish uses 4, Arabic uses 6. For each plural message, the file has msgstr[0], msgstr[1], etc., one per form. msgctxt disambiguates identical messages with different meanings (e.g. "File" as a menu vs. "File" as a document).

.po vs .pot vs .mo: which is which?

  • .pot — Portable Object Template. Generated from your source code by xgettext (or a framework wrapper like django-admin makemessages or wp i18n make-pot). All msgstr entries are empty. This is the file you hand to translators or import into a TMS.
  • .po — Portable Object. One per language. Translators fill in the msgstr entries. msgmerge updates an existing .po with new strings from the .pot.
  • .mo — Machine Object. The compiled binary form, produced by msgfmt. The runtime loads this. You translate .po; you ship .mo.

Editing .po files

.po is plain text and any editor opens it, but the format has subtle escaping rules and the plural-form numbering is easy to break. For real translation work, use a tool that understands the format:

  • Dedicated .po editors: Poedit (free, cross-platform), GTranslator (Linux/GNOME), Lokalize (KDE) — show source/target side by side, validate plurals, surface translator comments.
  • Cloud TMS platforms: Locize, Phrase, Crowdin, Weblate, Transifex — import .po, edit in a CAT-style UI with translation memory and glossary, export back to .po for the build.
  • Text editors: only for spot fixes. Don't edit complex plural entries by hand.

Common gettext workflows

  • WordPress / WordPress plugins. Use wp i18n make-pot to generate the .pot, hand the .pot to translators (or import to a TMS), receive translated .po files, compile to .mo with msgfmt, ship in languages/.
  • Django. django-admin makemessages generates .po stubs per locale, django-admin compilemessages compiles them to .mo. The TMS sits between makemessages and compilemessages.
  • Flask / generic Python. Babel + pybabel manage extraction and compilation. Same TMS-in-the-middle workflow.
  • C/C++ GNU apps. The original use case. Same xgettext / msgmerge / msgfmt cycle.

How to edit .po files in Locize

Locize imports and exports .po files. (Compile to .mo at build time with msgfmt; generate the initial .pot from your source code with xgettext or your framework's extractor.)

  1. Get started
  2. Import your .po file
  3. Edit translations in the CAT view with translation memory, glossary, and style guide applied automatically
  4. Use bulk actions for AI / machine translation, then route results through a review workflow
  5. Export back to .po — or convert to JSON, YAML, XLIFF, or any other supported format

Frequently asked questions

What is a .po file?

A .po file (Portable Object) is a plain-text translation file in the GNU gettext format. It pairs source-language messages (`msgid`) with their translations (`msgstr`) along with optional context, comments, and pluralization rules. Each language has its own .po file, typically named with the locale code (e.g. `de.po`, `fr.po`).

What is the difference between .po, .pot, and .mo?

`.pot` (Portable Object Template) is the source-only template — all `msgid` entries with empty `msgstr`, generated from your codebase. `.po` is the translated version of a .pot for one specific language. `.mo` (Machine Object) is the compiled binary form of a .po, produced by `msgfmt`, that the gettext runtime actually loads. You translate .po, you ship .mo.

How do I edit .po files?

For solo work: dedicated PO editors like Poedit (free, cross-platform) or GTranslator (Linux/GNOME). Many CAT tools and TMS platforms also import and export .po natively — Locize imports and exports the `.po` format directly. For spot fixes, .po is plain text and any editor works, but the format has subtle escaping rules so avoid hand-editing complex entries.

How do plural forms work in gettext?

A .po file declares its language's plural rules in the `Plural-Forms` header (e.g. `nplurals=2; plural=(n != 1);` for English). For each pluralized message, the file uses `msgid` + `msgid_plural` and one `msgstr[N]` per plural form. The runtime picks the right form based on the count.

Which projects use gettext?

WordPress (and the entire WordPress plugin / theme ecosystem), most Python web frameworks (Django, Flask), C/C++ GNU applications, GNOME and KDE desktop apps, Drupal, MediaWiki, Ruby on Rails (via gettext gem), and many others. It is the de-facto translation format in open-source software.

How do I generate a .pot template from my source code?

Run `xgettext` against your source files. For example: `xgettext -o messages.pot --keyword=_ src/**/*.py` extracts strings wrapped in `_(...)` calls into a .pot template. WordPress provides `wp i18n make-pot` via WP-CLI; Django provides `django-admin makemessages`. Each ecosystem has its own xgettext wrapper.

Can I convert .po to JSON or other formats?

Yes. CLI tools like `po2json` (npm), `translate-toolkit` (Python), and Locize's import/export pipeline can convert between .po and JSON, YAML, XLIFF, CSV, and most other translation formats while preserving plural rules and metadata.

Translate .po files without the spreadsheets.
Import, edit with translation memory, glossary and AI, review, and export — all in one place.