# How to Translate .po Files (gettext) Correctly | Vitra.ai

> In a .po file the msgid is an identifier, not a sentence. Editing it detaches the string from the code that requests it as well almost everywhere.

**Canonical URL**: https://www.vitra.ai/general/translate-po-files
**Source**: This is the Markdown rendering of https://www.vitra.ai/general/translate-po-files, generated at build time from that page.

---

4 min read

# How to Translate .po Files (gettext) Correctly

In a .po file the msgid is an identifier, not a sentence. Editing it detaches the string from the code that requests it as well almost everywhere.

[Samhitha J Bhatt](https://www.vitra.ai/author/samhitha)
Senior Product Manager , Vitra.ai
Updated Aug 17, 2026

![How to Translate .po Files (gettext) Correctly](https://www.vitra.ai/static/images/blog/translate-po-files.jpg)

Table of contents

[msgid in, msgstr out](#msgid-in-msgstr-out)

[Fuzzy entries mean "check me"](#fuzzy-entries-mean-check-me)

[Plural forms are per-language](#plural-forms-are-per-language)

[Placeholders and escapes](#placeholders-and-escapes)

[Context matters more than in most formats](#context-matters-more-than-in-most-formats)

[Why the second release is cheap](#why-the-second-release-is-cheap)

[The route through](#the-route-through)

[Obsolete entries accumulate, and that is fine](#obsolete-entries-accumulate-and-that-is-fine)

[Where to start](#where-to-start)

[FAQ](#faq)

Contributors

[Samhitha J Bhatt](https://www.vitra.ai/author/samhitha)
Senior Product Manager

Subscribe to our newsletter

Subscribe

> **Quick answer —** A .po file pairs a msgid with a msgstr. Only the msgstr gets translated — the msgid is the lookup key the application uses, and changing it silently detaches the translation from the code that asks for it.

## msgid in, msgstr out

The gettext format is a list of pairs. `msgid` is what the source code asks for, `msgstr` is what the user sees.

Translate the msgstr. Never the msgid. When the msgid changes, the application requests a string that no longer exists in the catalogue and falls back to the untranslated source, usually without any error at all.

## Fuzzy entries mean "check me"

A `#, fuzzy` flag marks a translation that gettext guessed from a similar string, typically after the source text was edited slightly.

Fuzzy strings are used at runtime in some configurations and ignored in others. Either way they are unreviewed, and treating them as finished is how a small source edit ships as a wrong translation.

## Plural forms are per-language

A .po header declares `Plural-Forms` with a rule and a count. English has two. Polish and Arabic have more, with different rules.

`msgstr[0]`, `msgstr[1]` and so on have to match the target language's rule, not the source's.

A translation that fills two forms into a six-form language leaves four empty and the application falls back for those cases.

## Placeholders and escapes

`%s`, `%d` and `%(name)s` are format specifiers consumed by code. They must survive exactly, and where a language needs a different word order the positional form `%1$s` exists for the purpose.

Reordering `%s` tokens without switching to positional form produces arguments substituted into the wrong slots — a bug that gets past every [linguistic review](https://www.vitra.ai/general/linguistic-qa-checklist).

## Context matters more than in most formats

`msgctxt` disambiguates identical strings used differently. "Open" as a verb on a button and "Open" as a status label are the same characters and different words in most languages. Without context, a translator sees one string and picks one rendering, and one of the two places is wrong. Where the developer supplied context, use it.

## Why the second release is cheap

Software catalogues change incrementally. Between releases, most msgids are untouched. [translation memory](https://www.vitra.ai/features/translation-memory) returns those instantly and only the genuinely new strings need work, which is the whole reason continuous localization is practical for software and not for print.

## The route through

Upload the .po or connect the repository, protect msgid and format specifiers, set the plural rule per language, translate, review. [document translation](https://www.vitra.ai/features/document-translation) handles the catalogue and [quality control](https://www.vitra.ai/features/quality-control) checks the placeholders survived. Details are on the [PO translation integration](https://www.vitra.ai/integration/po-translation) page. The template file has its own notes on the [POT integration](https://www.vitra.ai/integration/pot-translation) page.

## Obsolete entries accumulate, and that is fine

Strings removed from the code stay in the catalogue commented out with a hash tilde prefix. They are dead weight, and translating them spends effort on text no user will ever see.

They are also insurance. If a string returns in a later release its old translation is still sitting there to be recovered, so purging them aggressively costs more than it saves.

## Where to start

One catalogue, one language, then diff. Every msgid identical and every msgstr populated means the pipeline is right.

## FAQ

**What gets translated in a .po file?** Only the msgstr. The msgid is the lookup key the source code requests, so changing it detaches the translation and the application falls back to untranslated text, usually with no error.

**What does a fuzzy flag mean?** That gettext guessed the translation from a similar string, normally after a small source edit. Fuzzy entries are unreviewed and are used at runtime in some configurations, so treating them as finished ships wrong translations.

**How do plural forms work in gettext?** The .po header declares a Plural-Forms rule and count for the target language. English has two forms and languages like Polish and Arabic have more, so the target file must carry its own number of msgstr entries.

**Why does msgctxt matter?** Because identical English strings often need different translations. Open as a button verb and Open as a status label are the same characters and different words in most languages, and context is what separates them.

Our blog

## Lastest blog posts

Tool and strategies modern teams need to help their companies grow.

Automotive

[Automotive Brochure Localization by Market](https://www.vitra.ai/automotive/automotive-brochure-localization)
A car brochure is a spec grid, a legal footer and a photo library, all market-specific. What actually has to change, and why the layout decides the schedule.

[Samhitha J Bhatt](https://www.vitra.ai/author/samhitha)
Aug 18, 2026

Automotive

[Automotive Campaign Localization Across Markets](https://www.vitra.ai/automotive/automotive-campaign-localization)
Campaigns run through national companies and dealer networks, so one master becomes hundreds of files. Where the offer text and the disclaimers actually break.

[Samhitha J Bhatt](https://www.vitra.ai/author/samhitha)
Aug 18, 2026

Automotive

[Car Service Manual Translation for Technicians](https://www.vitra.ai/automotive/automotive-service-manual-translation)
A workshop manual is read mid-repair by someone with the car on a lift. What that demands of procedures, torque figures and fault codes, in every language.

[Samhitha J Bhatt](https://www.vitra.ai/author/samhitha)
Aug 18, 2026

[View all posts](https://www.vitra.ai/blog/page/1)

---

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "Article",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.vitra.ai/general/translate-po-files"
  },
  "headline": "How to Translate .po Files (gettext) Correctly",
  "image": [
    {
      "@type": "ImageObject",
      "url": "https://www.vitra.ai/static/images/blog/translate-po-files.jpg"
    }
  ],
  "datePublished": "2026-08-17T00:00:00.000Z",
  "dateModified": "2026-08-17T00:00:00.000Z",
  "author": [
    {
      "@type": "Person",
      "name": "Samhitha J Bhatt"
    }
  ],
  "publisher": {
    "@type": "Organization",
    "name": "Vitra.ai",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.vitra.ai/static/images/vitra-v-logo.png"
    }
  },
  "description": "In a .po file the msgid is an identifier, not a sentence. Editing it detaches the string from the code that requests it as well almost everywhere."
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://www.vitra.ai"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "General",
      "item": "https://www.vitra.ai/general"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "How to Translate .po Files (gettext) Correctly",
      "item": "https://www.vitra.ai/general/translate-po-files"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What gets translated in a .po file?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Only the msgstr. The msgid is the lookup key the source code requests, so changing it detaches the translation and the application falls back to untranslated text, usually with no error."
      }
    },
    {
      "@type": "Question",
      "name": "What does a fuzzy flag mean?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "That gettext guessed the translation from a similar string, normally after a small source edit. Fuzzy entries are unreviewed and are used at runtime in some configurations, so treating them as finished ships wrong translations."
      }
    },
    {
      "@type": "Question",
      "name": "How do plural forms work in gettext?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "The .po header declares a Plural-Forms rule and count for the target language. English has two forms and languages like Polish and Arabic have more, so the target file must carry its own number of msgstr entries."
      }
    },
    {
      "@type": "Question",
      "name": "Why does msgctxt matter?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Because identical English strings often need different translations. Open as a button verb and Open as a status label are the same characters and different words in most languages, and context is what separates them."
      }
    }
  ]
}
```
