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.
- 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-Formsheader (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 byxgettext(or a framework wrapper likedjango-admin makemessagesorwp i18n make-pot). Allmsgstrentries are empty. This is the file you hand to translators or import into a TMS..po: Portable Object. One per language. Translators fill in themsgstrentries.msgmergeupdates an existing .po with new strings from the .pot..mo: Machine Object. The compiled binary form, produced bymsgfmt. 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-potto generate the .pot, hand the .pot to translators (or import to a TMS), receive translated .po files, compile to .mo withmsgfmt, ship inlanguages/. - Django.
django-admin makemessagesgenerates .po stubs per locale,django-admin compilemessagescompiles 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.)
- Get started
- Import your
.pofile - Edit translations in the CAT view with translation memory, glossary, and style guide applied automatically
- Use bulk actions for AI / machine translation, then route results through a review workflow
- Export back to
.po, or convert to JSON, YAML, XLIFF, or any other supported format
Frequently asked questions
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`).
`.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.
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.
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.
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.
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.
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.