Skip to main content
This tutorial connects a Gettext-based backend to Hyperlocalise Platform. Your extraction tool remains responsible for code scanning and the .pot template. Hyperlocalise syncs the resulting .po catalogs for translation and review. Hyperlocalise reads .po and .pot catalogs, including msgctxt, translator comments, and gettext plural forms. Plural strings are one entry per category (one, other, and the extra categories the target language needs). Writeback fills msgstr[n] for that language and updates the catalog header.

Prerequisites

You need:
  • a backend that uses GNU Gettext-compatible catalogs;
  • xgettext, msginit, and msgmerge;
  • the Hyperlocalise CLI;
  • a Platform project with en-US as source and fr-FR and de-DE as targets; and
  • HYPERLOCALISE_API_KEY and HYPERLOCALISE_PROJECT_ID in your environment.
The example uses:

1. Mark backend messages

The exact API depends on your language. A Python service might use:
Keep placeholders named and stable. Hyperlocalise can compare {name} between source and target. Add translator comments when your extraction tool supports them:

2. Extract a POT template

Run the command appropriate for your backend. For Python:
The POT file is the extraction template:
Commit messages.pot. You can map the .pot file directly. Hyperlocalise reads an empty msgstr as the msgid text. A source-language .po whose msgstr already holds the English text works the same way.

3. Create and update the source PO catalog

Create the English catalog once:
When code changes, update it:
Hyperlocalise reads msgid as the stable key. A populated msgstr is the source text. An empty msgstr falls back to msgid, which is what a .pot template contains. msgctxt is part of the key (context||msgid). Plural forms use keys such as msgid||one and msgid||other.

4. Map PO catalogs

Create i18n.yml:
Keep locale selection in the directory structure. After pull, check the PO header expected by your runtime; reconstruction uses the source PO as the structural template.

5. Check extraction drift

In the source pull request, regenerate the template and update the source PO:
Put the exact xgettext and msgmerge commands in scripts/extract-messages.sh so local and CI extraction agree. This gate catches messages added in code without an updated catalog.

6. Push and review messages

After merge:
Start translation through a Source upload automation or New Request. Review:
  • placeholders such as {name} or %s;
  • Unreal Engine rich-text tags such as <Highlight>Ready</> and <img id="Health"/>;
  • translator comments and source references;
  • punctuation produced by the surrounding code;
  • terminology shared with API errors and emails; and
  • strings that may reach logs instead of end users.
Do not send internal diagnostics, secrets, or user data for translation. Approve the target catalogs after review.

7. Pull and validate PO files

Run:
msgfmt validates PO syntax and format strings. Inspect each target header after pull and set runtime-specific Language, encoding, or domain metadata when your framework requires it. Compile catalogs as part of the build:
Generate .mo files during build or release. Avoid committing them unless your deployment process requires compiled catalogs in git.

8. Test the backend

Open a translation pull request and test:
  • locale negotiation selects the expected catalog;
  • placeholders interpolate without exceptions;
  • missing translations follow your intended fallback;
  • API responses preserve machine-readable error codes; and
  • translated emails or HTML remain escaped correctly.
Keep protocol identifiers and error codes outside translatable msgid values.

Troubleshooting

Context variants used to overwrite each other

They no longer do. Each msgctxt is part of the key: nav||Open and forum||Open are different strings. The first run after this change translates context-specific and plural strings again, because their keys changed.

A plural file has more msgstr lines than the source

That is expected for languages with more plural categories than the source. Arabic gets six forms. Categories without their own translation are filled from other. The header Plural-Forms line matches the target language.

Empty msgstr became the msgid text

That is the .pot and Crowdin default. Put the real source text in msgstr, or set X-Crowdin-SourceKey: msgstr in the header when an empty msgstr must stay empty.

Unreal Engine rich-text tags look like chips

That is expected. Unreal style tags close with nameless </>. Hyperlocalise shows each recognized tag as a placeholder chip such as UE#0. Keep every chip and translate the words between them. {ItemName} inside a style pair stays a separate placeholder. Downloaded .po files contain the original Unreal markup. See Unreal Engine rich text in gettext PO for the tag grammar Hyperlocalise recognizes.

Next