Internationalisation (gettext)¶
XCSoar uses GNU gettext for
UI strings. Catalogues live in po/ (33 languages plus the
xcsoar.pot template). Macros are defined in
src/Language/Language.hpp.
Marking strings in source¶
_("Text")— translate immediately at the call site.N_("Text")— mark for extraction only (static arrays / initialisers); translate later withgettext().AddChoices()does this forStaticEnumChoicelabels.C_("Context", "Text")/NC_("Context", "Text")— same as_()/N_(), with a gettextmsgctxtso identical English strings can be disambiguated by role.
Do not mark log messages for translation
(LogFormat(), LogDebug(), LogInfo(), LogError(),
and similar). Keep logs in English for consistency and debugging.
Reuse existing English strings whenever the same wording fits.
Slightly different spellings or punctuation create extra catalogue
entries and more work for every language. Prefer matching an existing
msgid (or an existing msgctxt + msgid pair) over inventing
a near-duplicate. Use msgctxt only when the same English text
genuinely needs different translations by role.
When to use msgctxt¶
Use context for short, ambiguous UI labels that share English wording but mean different things (button vs setting vs status). Prefer existing context names:
Button,Menu,Setting,StatusWeather control,Weather layer,Abbreviation,InfoBox
Examples:
C_("Button", "Setup")
NC_("Setting", "Auto")
C_("Status", "Live")
C_("Weather control", "Time")
Avoid inventing new context names when an existing one fits.
Updating catalogues¶
After adding or changing marked strings:
make update-po TARGET=UNIX
This regenerates po/xcsoar.pot and merges into every
po/*.po.
What make update-po does¶
Implemented in build/gettext.mk:
xgettext/msgcatwith--no-wrapand--no-location(keywords_,N_,C_,NC_) →po/xcsoar.potFor each language file:
msgmerge --no-wrap --no-location --previousmsgattrib --clear-fuzzy --empty --no-obsolete --clear-previoustools/update_po_low_churn.pypatches only changed entries
#: file:line locations are omitted on purpose. They churn on every
source edit and are not required at runtime.
Low-churn policy¶
Routine catalogue updates must not rewrap or rewrite untouched entries.
tools/update_po_low_churn.py:
Rewrites only entries whose translation or key changed
Keeps original
msgid/msgstrquote wrapping where possibleStrips leftover
#:linesUpgrades 1:1 bare →
msgctxtadditions in place when possibleFalls back safely if entry block spans overlap
Never bulk-save a whole .po with polib (or any tool that
rewraps the file). That destroys low-churn history and produces huge
diffs. Edit msgstr values with targeted replacements, or go through
make update-po / update_po_low_churn.py.
Filling translations¶
Run
make update-po.Fill empty
msgstrentries (all 33 languages when possible).Validate:
msgfmt --check -o /dev/null po/<lang>.po
Spot-check placeholders (
%s,%u, …) match themsgid.Avoid
msgstrtext that looks like printf tokens unless themsgidhas the same placeholders (e.g. Hungarian50%-nálcan be misread as%-n; prefer a spelled-out “percent” form).
Keep brand names such as SkySight and NetCDF unchanged unless a
language already has an established local form.
Commit organisation¶
Small fixes: one
po:commit (orpo/<lang>:for a single file), usually at the end of a feature branch.Large refreshes:
po: Regenerate translation catalog(xcsoar.pot, and any source fix required for extraction)One
po/<lang>: Refresh translationscommit per language
Each language file should be touched once on a refresh branch when practical. Fold follow-up msgstr fixes into that language’s commit.
.po syntax notes¶
Never put custom comments between
#, fuzzyandmsgid, or betweenmsgidandmsgstr.Place comments before
#, fuzzyor at the start of an entry block.Invalid syntax breaks
msgmergeduringmake update-po.