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. translate is 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-compare messages.pot with the core POT and switch any newly-covered msgid to xlate().

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 → .mo step is obsolete (translation format switch, issue #88).

Last modified November 2, 2026: applied repoFileURL, where applicable (21fa655)