Translation Pipeline
How translatable strings flow from source to runtime (xgettext → Weblate → PO → PHP)
The module ships its own translations through the standard gettext chain, hosted on Codeberg Weblate.
Flow
flowchart TB
subgraph Extraction["1️⃣ Code → POT"]
direction LR
PHP["📝 src/*.php"] -->|npx gulp updatepo| POT["📄 messages.pot"]
end
subgraph Translation["2️⃣ Translation"]
direction LR
WEBLATE["🌐 Weblate"] -->|download| PO["🗝 <lang>.po"]
end
subgraph Rendering["3️⃣ Webtrees"]
direction LR
PHPARR["📦 lang.php"] -->|I18N| RENDER["✅ Render"]
end
POT -->|upload| Translation
PO -->|npx gulp po2php| Rendering
classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px
classDef fileStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px
classDef weblateStyle fill:#f3e5f5,stroke:#7B1FA2,stroke-width:2px
classDef renderStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px
class PHP sourceStyle
class POT,PO,PHPARR fileStyle
class WEBLATE weblateStyle
class RENDER renderStyle
Extracting strings
update-po-files.sh
runs xgettext over every *.php / *.phtml except util/, vendor/, node_modules/ and tests/:
xgettext -L PHP \
--keyword=translate --keyword=plural:1,2 --keyword=translateContext:1c,2 \
--add-comments=I18N --from-code=utf-8 \
--output=resources/lang/messages.pot …
Two marker helpers control what gets extracted:
I18N::translate()/MoreI18N::translate()— module-specific strings.translateis an xgettext keyword, so these are picked up.MoreI18N::xlate()— strings already covered by the webtrees core POT. Same runtime behaviour, different name, therefore invisible to xgettext. After a core update, re-comparemessages.potwith the core POT and switch any newly-covered msgid toxlate().
Add /* I18N: … */ comments to give translators context (picked up via --add-comments=I18N).
PO → PHP
.po files are not merged locally — that is Weblate’s job, to prevent conflicts. Once Weblate delivers updated .po files, compile them:
npx gulp po2php
Note: a former
.po→.mostep is obsolete (translation format switch, issue #88).