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.

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.
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 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 handles the catalogue and quality control checks the placeholders survived. Details are on the PO translation integration page. The template file has its own notes on the POT integration 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
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.

Automotive
Automotive Campaign Localization Across Markets
Campaigns run through national companies and dealer networks, so one master becomes hundreds of files. Where the offer text and the disclaimers actually break.

Automotive
Car Service Manual Translation for Technicians
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.