Translator Contributor Guide

How to contribute translations and PO file conventions

3 min read 530 words

This guide explains how to contribute translations to a Bengal site using the standard gettext PO workflow.

PO File Structure

Translations live in:

i18n/
├── en/LC_MESSAGES/messages.po
├── es/LC_MESSAGES/messages.po
└── ar/LC_MESSAGES/messages.po

Each.pofile contains:

  • Header: Metadata (charset, plural forms)
  • Entries:msgid (source) and msgstr(translation)

PO File Format

# English translations for My Site
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"MIME-Version: 1.0\n"

msgid "Home"
msgstr "Home"

msgid "About"
msgstr "About"

msgid "Read more"
msgstr "Read more"
  • msgid: The source string (from templates)
  • msgstr: The translation. Emptymsgstr ""means untranslated (fallback to key)

Conventions

  1. Use UTF-8 for all PO files. SetContent-Type: text/plain; charset=UTF-8in the header.
  2. Keep msgid unchanged — it's the lookup key. Never translate the msgid.
  3. Preserve placeholders — if the source has{name}, keep it in the translation: Hola {name}.
  4. Plural forms — usemsgid_plural and msgstr[0], msgstr[1]for languages with plural rules.

Workflow for Contributors

  1. Get the template: Ask the maintainer formessages.pot or run bengal i18n extractin the repo.
  2. Create your locale:mkdir -p i18n/XX/LC_MESSAGES (e.g. XX = frfor French).
  3. Copy and translate: Copymessages.pot to i18n/XX/LC_MESSAGES/messages.po and fill in msgstrvalues.
  4. Submit: Open a PR with your new or updated.pofile.

Tools

  • Poedit: GUI editor for PO files, handles plural forms
  • Lokalize: KDE translation tool
  • VS Code: Extensions like "Gettext" for PO editing

Checking Your Work

After adding translations:

bengal i18n compile
bengal build
bengal i18n status

bengal i18n statusshows coverage. Aim for 100% for your locale.

Plural Forms

Bengal supports plural-aware translation through thent()template function.

Using nt() in Templates

{# Basic plural — {n} is automatically replaced with the count #}
{{ nt('1 item', '{n} items', count) }}

{# With extra parameters #}
{{ nt('1 {thing}', '{n} {thing}s', count, {'thing': 'file'}) }}

When no gettext catalog is loaded, nt() uses English-style selection (singular when n=1, plural otherwise). With a catalog, it uses ngettext()for correct plural rules.

PO File Plural Entries

For languages with complex plural rules, usemsgid_plural and indexed msgstr:

# Spanish (2 forms: singular, plural)
msgid "1 item"
msgid_plural "{n} items"
msgstr[0] "1 elemento"
msgstr[1] "{n} elementos"
# Polish (3 forms: singular, few, many)
msgid "1 item"
msgid_plural "{n} items"
msgstr[0] "1 element"
msgstr[1] "{n} elementy"
msgstr[2] "{n} elementów"
# Arabic (6 forms: zero, one, two, few, many, other)
msgid "1 item"
msgid_plural "{n} items"
msgstr[0] "لا عناصر"
msgstr[1] "عنصر واحد"
msgstr[2] "عنصران"
msgstr[3] "{n} عناصر"
msgstr[4] "{n} عنصرًا"
msgstr[5] "{n} عنصر"

Plural Rules Reference

The correct number of forms depends on the language:

Language Forms Rule
English, Spanish, French 2 n == 1 ? singular : plural
Polish, Czech, Russian 3 Complex (singular, few, many)
Arabic 6 Complex (zero through other)
Japanese, Chinese, Korean 1 No plural distinction

See Unicode CLDR Plural Rules for the full reference.