Handling Variables & Placeholders in PO Files (%s, printf, {name})
A placeholder is the one part of a msgstr that a program reads, and this guide is built on what each family actually does when a translation breaks it, executed in Python 3.14, PHP 8.2 and i18next 26 rather than recalled. Opens with a one-line inventory of every placeholder token in a catalog, then a map of the families — printf, numbered %1$s, named %(name)s, brace {name}, Ruby, shell, Qt — against the four that gettext has no flag for and msgfmt therefore cannot check: i18next double braces, template-literal ${name}, ICU plurals and HTML tags, each shown passing msgfmt -c with a renamed or dropped placeholder. The centre is a runtime matrix for five translator mistakes. Dropping a placeholder is the common failure and the quiet one: it deletes the filename in every named style and in PHP without an error, while adding or renaming one raises TypeError, KeyError, IndexError or ArgumentCountError, or leaves a raw {{datei}} on screen. %s changed to %d prints 0 in PHP. Then reordering, where the syntax decides: %2$s: %1$s works in C and PHP but raises ValueError in a Python % string, {1} and {0} work in str.format even when the source says {}, and msgfmt is stricter than the runtimes, rejecting a dropped named placeholder that Python runs silently. The extraction section is the one most guides skip: _(“Hello %s” % name) extracts cleanly and can never match at runtime, f-strings, JavaScript template literals and PHP double-quoted interpolation extract nothing and warn about nothing, and concatenation extracts fragments, with the four-pattern grep that caught every bad line. Plus literal percent signs (a bare % turned Heute 50% sparen into Heute 50a.txtparen), the no-c-format source comment versus the global --flag that also disables real checks, --add-comments translator notes that survive msgmerge, and a ten-row symptom table.