Translations
This guide is the single starting point for everything related to translating aMule. There are three areas that can be translated: the application interface strings (which also cover the Windows installer), the man pages, and the website documentation.
Git is the source of truth for all translations, which live across two repositories: the application interface strings and the man pages are in amule-org/amule, and the website documentation is in amule-org/amule-org.github.io.
There are two equally valid ways to contribute a translation: open a pull request to the relevant repository, or use Weblate — a translation tool that stays in sync with git. Both edit the same files and either is accepted — pick whichever you prefer. The Weblate guide documents how each module is connected; the sections below describe the underlying file formats and the manual (pull request) workflow.
Code Translations
aMule's interface strings are managed with GNU gettext. Translations are stored as .po files in po/ and compiled to binary .mo catalogs at build time. The interface is currently maintained in several dozen languages — po/LINGUAS is the canonical list of the codes that get compiled. This document explains how to contribute a new or updated translation.
Overview
| File | Purpose |
|---|---|
po/amule.pot | Template with all translatable strings extracted from source. Empty translations. Committed to git. |
po/xx.po | One file per language. Contains the actual translations. Committed to git. |
build/po/xx.gmo | Compiled binary catalog, generated at build time. Not committed. |
At runtime, the program loads the installed amule.mo catalog for the user's locale. The catalog is always named amule.mo, but its install location depends on the platform: Windows uses bin/locale/xx/LC_MESSAGES/, macOS bundles it inside the app at Contents/Resources/locale/xx/LC_MESSAGES/, and Linux/BSD use share/locale/xx/LC_MESSAGES/.
po/
├── amule.pot # master template (regenerated by update-po.sh)
├── POTFILES.in # list of source files scanned for translatable strings
├── LINGUAS # list of language codes that get compiled
├── Makevars # xgettext options (keyword list, package metadata)
├── de.po # German
├── fr.po # French
├── pt_BR.po # Portuguese (Brazil)
└── ... # one .po file per language
Prerequisites
Install the gettext tools. They provide xgettext, msgmerge, msgfmt, and msgattrib:
# Debian / Ubuntu
sudo apt install gettext
# Fedora / RHEL
sudo dnf install gettext
# Arch / Manjaro
sudo pacman -S gettext
# macOS (Homebrew)
brew install gettext
A dedicated PO editor makes the translation process much easier. Poedit is the most widely used cross-platform option. Lokalize (KDE) is another good choice. Plain text editors also work — .po files are standard UTF-8 text.
Checking Translation Status
Summary — how many strings are translated, fuzzy, or missing:
msgfmt --statistics po/pt_BR.po -o /dev/null
Example output:
1823 translated messages, 42 fuzzy translations, 17 untranslated messages.
List only untranslated strings:
msgattrib --untranslated po/pt_BR.po
List only fuzzy strings:
msgattrib --only-fuzzy po/pt_BR.po
Updating an Existing Translation
After source code changes, the .pot template and all .po files must be refreshed. Run this from the repository root:
./scripts/update-po.sh
This script:
- Runs
xgettextover all files listed inpo/POTFILES.into regeneratepo/amule.pot. - Updates the copyright year in the
.potheader. - Runs
msgmerge --updateon everypo/*.poso each language file receives new and changed strings.
CI runs the same script on every pull request and fails if the committed catalogs are out of sync with the source; it also checks that every .po file compiles.
After running update-po.sh, each .po file may contain:
- Untranslated entries —
msgstr ""is empty; the string is new and has not yet been translated. - Fuzzy entries — marked
#, fuzzy;msgmergefound a similar existing translation as a starting point, but it requires human review. The program falls back to the English original until the fuzzy marker is removed. - Translated entries —
msgstris filled in with no fuzzy marker; ready to ship.
Open the target .po file in Poedit (or any PO editor), fill in or correct the msgstr fields, remove fuzzy markers after reviewing them, and commit the result.
Adding a New Language
-
Create the initial
.pofile from the template:msginit --input=po/amule.pot --locale=xx --output=po/xx.poReplace
xxwith the locale code (e.g.nl,ko_KR,pt_BR). -
Add the locale code to
po/LINGUAS(one code per line, alphabetically sorted). -
Translate the strings in
po/xx.po. -
Verify the file compiles without errors:
msgfmt --check po/xx.po -o /dev/null -
Commit both
po/xx.poand the updatedpo/LINGUAS, then open a pull request.
Testing Your Translation
To verify a translation at runtime, set LANG before launching aMule. Use LANG, not LANGUAGE — the latter does not work reliably with wxWidgets:
LANG=pt_BR.UTF-8 amule
The program must be installed first so the .mo catalog is discoverable. See the Compilation guide for the full build and install workflow. During development, install locally without sudo:
cmake --install build --prefix=$HOME/.local
LANG=pt_BR.UTF-8 amule
To uninstall when done:
xargs rm -f < build/install_manifest.txt
Build Integration
The CMake build compiles every enabled .po into a .gmo binary catalog via msgfmt. The Compilation guide documents the full set of build options; translation support requires -DENABLE_NLS=YES (the default):
cmake -B build -DENABLE_NLS=YES
To build only a subset of languages:
cmake -B build -DENABLE_NLS=YES -DTRANSLATIONS="de,fr,pt_BR,"
Keep the trailing comma: po/CMakeLists.txt matches each language code followed by a comma, so without it the last language in the list is silently skipped.
Windows Installer Strings
The .po catalogs also cover the Windows installer (an NSIS .exe). Its standard wizard pages, buttons, and error dialogs come from NSIS's own bundled language files and need no translation work; the aMule-specific strings (section names, component descriptions, runtime messages) appear in po/<lang>.po next to the application strings, marked #: packaging/windows/installer_strings.c:<line>. Translate them like any other entry — at installer build time, packaging/windows/po-to-nsh.py converts them into the per-language NSIS LangString blocks automatically.
Specific to these strings:
- Keep
$INSTDIR,$0, and$APPDATAverbatim — they are NSIS runtime variables, evaluated at install time. - Keep the
\r\nescapes; the bridge converts them to NSIS's$\r$\nline breaks. %APPDATA%(with percent signs) is a literal Windows environment-variable reference shown to the user, not a format placeholder — keep it as text.- Only locales that map to an NSIS language are bundled (the mapping table is at the top of
po-to-nsh.py); untranslated entries, and locales without an NSIS counterpart, fall back to English.
For developers, adding or renaming an installer string means updating three files in sync — the _("...") entry in packaging/windows/installer_strings.c (extraction-only, never compiled), the ${LANG_ENGLISH} LangString baseline and its $(MYSTR_*) call site in packaging/windows/installer.nsi, and the KEYS list in po-to-nsh.py — then running ./scripts/update-po.sh.
Marking Strings for Translation in C++ Source
Use the standard gettext macros:
| Macro | Use case |
|---|---|
_("text") | Most common — translates a single string |
wxTRANSLATE("text") | Marks a string for extraction without translating it at that point |
wxPLURAL("one item", "%d items", n) | Plural forms |
After adding new uses of these macros, run ./scripts/update-po.sh to pull the new strings into the .pot and all .po files.
When adding a new .cpp source file with translatable strings, add it to po/POTFILES.in. xgettext only scans files listed there — the omission is completely silent. New source changes should also follow the project Coding Style.
Format Specifiers Reference
aMule translatable strings sometimes contain format specifiers and escape codes. These must be preserved exactly as-is in translations — only the surrounding natural-language text should change.
Escape Codes
Escape codes are character sequences that represent special characters:
| Code | Meaning |
|---|---|
\n | New line |
\t | Horizontal tab |
\r | Carriage return |
\\ | Literal backslash (\) |
\" | Literal double quote (") |
\' | Literal single quote (') |
\a | Audible alert (beep) |
\b | Backspace |
\f | Form feed |
\? | Question mark (avoids trigraph interpretation) |
\<octal> | Character with the given octal value |
\x<hex> | Character with the given hexadecimal value |
Format Specifiers
Format specifiers are replaced at runtime by variable values:
| Specifier | Replaced by |
|---|---|
%s | A string |
%d or %i | A signed decimal integer |
%u | An unsigned decimal integer |
%f | A floating-point number (normal notation) |
%e / %E | A floating-point number (exponential notation) |
%g / %G | A floating-point number (automatic notation) |
%x / %X | A hexadecimal integer (lower/uppercase) |
%o | An octal integer |
%c | A single character |
%p | A memory address |
%% | A literal % character |
Type extensions that may appear between % and the type letter:
| Extension | Effect |
|---|---|
h | Short integer (hd, hu, hx, etc.) |
l | Long integer (ld, lu, lx, etc.) |
L | Long double (Lf, Lg, etc.) |
Output tweaks between % and the type letter:
| Tweak | Effect |
|---|---|
- | Left-align |
+ | Always print sign |
0 | Zero-pad the number |
# | Prefix 0 for octal, 0x/0X for hex |
<N> | Minimum field width |
.<N> | Precision (decimal places for floats; max chars for strings) |
You must never change the order of format specifiers. The order in the translation must match the order in the English original. Swapping %s and %d will cause aMule to crash when displaying the string.
The & Accelerator Character
An & before a letter in a menu or button string marks the keyboard accelerator (the underlined letter). The & must be placed before the same letter in the translation (not necessarily the same position in the sentence):
English: "Insert eD2k &Link to clipboard" → accelerator: L
German: "eD2k-&Link in die Zwischenablage" → accelerator: L (same letter)
Leading and Trailing Spaces
Some strings begin or end with a space. This is intentional — the space may be used to separate the translated string from other text appended at runtime. Do not remove it:
English: " aMule version "
Good: " aMule versión "
Bad: "aMule versión" ← missing leading/trailing spaces
Example
msgid "I am %s and I am %d years old."
msgstr "Ich bin %s und ich bin %d Jahre alt."
Both %s and %d are preserved in the same order.
Plural Forms
Plurals are handled differently across languages. English and German use singular/plural; Turkish has no distinction; Polish uses different forms for different numbers.
The Plural-Forms header in the .po file declares the plural rules for the language. When creating a new .po with msginit, this header is set automatically. If in doubt, consult the gettext plural forms reference.
Example (German):
"Plural-Forms: nplurals=2; plural=n != 1;\n"
Plural strings in the .po file:
#: src/kademlia/routing/RoutingZone.cpp:141
#, c-format
msgid "Read %u Kad contact"
msgid_plural "Read %u Kad contacts"
msgstr[0] "Lese %u Kad-Kontakt"
msgstr[1] "Lese %u Kad-Kontakte"
Common .po Compilation Errors
| Error | Cause | Fix |
|---|---|---|
end-of-line within string | A string opened with " was not closed. | Find the line in the error and close the string. |
format specifications in 'msgid' and 'msgstr' for argument N are not the same | A %s/%d was changed or missing. | Check all % sequences in the translation match those in the English. |
number of format specifications does not match | The count of % sequences differs. | Count carefully — you may have typed &s instead of %s. |
'msgid' and 'msgstr' entries do not both end with '\n' | Different number of newlines. | Match the number of \n sequences exactly. |
Utility Script: Find the msgid Containing a Given Error Line
When msgfmt reports an error at a specific line number, this Python 3 script tells you which msgid block contains that line:
#!/usr/bin/env python3
"""Find the msgid number that contains a given line number in a .po file."""
import sys
if len(sys.argv) < 3:
print(f"usage: {sys.argv[0]} <po_file> <line_number>")
sys.exit(1)
file_name = sys.argv[1]
line_number = int(sys.argv[2])
msgid_count = 0
with open(file_name, encoding="utf-8") as po_file:
for i, line in enumerate(po_file, start=1):
if line.startswith("msgid"):
msgid_count += 1
if i == line_number:
print(f"error at line {line_number} is in msgid no. {msgid_count - 1}")
sys.exit(0)
print(f"line {line_number} not found in {file_name}")
sys.exit(1)
Save this as find_msgid.py and run:
python3 find_msgid.py po/de.po 142
Man Page Translations
aMule's man pages can also be translated using po4a. The translation files live in docs/man/po/. The man pages are currently translated into a smaller set of languages than the interface — the [po4a_langs] line in docs/man/po4a.config.in is the canonical list.
Unlike in older releases, the rendered translated man pages (amule.de.1.in, alc.fr.1.in, …) are not committed to git: the build renders them at build time with po4a from the English masters plus the .po files (see Build Integration). Translators only edit the .po files.
File Layout
| File | Purpose |
|---|---|
docs/man/*.1.in, src/utils/*/docs/*.1.in (no language suffix) | English masters. Edit these to change the man page content. Committed to git. |
docs/man/po/manpages.pot | Template extracted from the English masters. Regenerated by scripts/update-manpages-po.sh. Committed to git. |
docs/man/po/manpages-<lang>.po | One file per language. Contains the actual translations. Committed to git. |
docs/man/po/manpages-<lang>.add | Optional addendum with the translator credit. Committed to git. |
*.<lang>.1.in (with language suffix) | Rendered translated man pages. Not committed — generated in the build directory at build time, or shipped pre-rendered in the source bundle attached to each GitHub Release. |
docs/man/
├── amule.1.in # English man page (master)
├── amuled.1.in
├── amulecmd.1.in
├── amulegui.1.in
├── amuleweb.1.in
├── ed2k.1.in
├── po4a.config.in # po4a configuration (CMake template)
└── po/
├── manpages.pot # master template
├── manpages-de.po # German translation
├── manpages-es.po # Spanish translation
├── manpages-de.add # German addendum (translator credit)
└── ...
The same po4a.config.in and manpages-<lang>.po files also drive the man pages of the standalone utilities, whose masters live next to their code: alc and alcc (src/utils/aLinkCreator/docs/), cas (src/utils/cas/docs/), and wxcas (src/utils/wxCas/docs/). There is one set of .po files for all man pages — utilities included.
Prerequisites
Install po4a (optional but strongly recommended — it automates file generation):
# Debian / Ubuntu
sudo apt install po4a
# Fedora / RHEL
sudo dnf install po4a
Rules
- Do not add or remove information compared to the English original. If you believe the English text is wrong or incomplete, fix the English source first.
- Your editor must produce UTF-8 files.
- Do not remove special characters (
B<...>,I<...>, etc.) — they are formatting markers. - po4a ignores files where fewer than 80% of strings are translated.
- Do not remove trailing spaces.
Updating the Template After Editing an English Master
After editing any *.1.in English master, the template and all .po files must be refreshed. Run this from the repository root (requires po4a and gettext):
./scripts/update-manpages-po.sh
This regenerates docs/man/po/manpages.pot and runs msgmerge on every docs/man/po/manpages-*.po so each language file receives the new and changed strings. Commit the resulting changes together with the master edit. It is the man-page counterpart of ./scripts/update-po.sh for the application catalogs.
CI runs the same script on every pull request and fails if the committed catalogs have drifted from the English masters.
Starting a New Man Page Translation
-
Add your language code to the
[po4a_langs]line indocs/man/po4a.config.in(alphabetically). For example, to add Dutch, changede es fr hu it pt_BR ro ru tr zh_TWtode es fr hu it nl pt_BR ro ru tr zh_TW. -
Create the initial
.pofile from the template:msginit --input=docs/man/po/manpages.pot --locale=nl --output=docs/man/po/manpages-nl.po -
Translate the strings in
docs/man/po/manpages-nl.po. -
Commit both
docs/man/po/manpages-nl.poand the updatedpo4a.config.in.
po4a Formatting Codes
The formatting characters in po4a files differ from raw man page markup:
| Code | Meaning |
|---|---|
E<lt> | Prints < |
E<gt> | Prints > |
B<text> | Text printed in bold |
I<text> | Text printed in italic |
R<text> | Text printed in normal (roman) font |
Example (keep B<> and I<> wrappers, only translate the text inside E<lt>pathE<gt>):
msgid "B<[ -w> I<E<lt>pathE<gt>>, B<--use-amuleweb>=I<E<lt>pathE<gt>> B<]>"
msgstr "B<[ -w> I<E<lt>PfadE<gt>>, B<--use-amuleweb>=I<E<lt>PfadE<gt>> B<]>"
Addendum File
The addendum is an optional file docs/man/po/manpages-<lang>.add that adds a translator credit at the bottom of every translated man page.
Copy an existing addendum (e.g. manpages-de.add) and adapt the content. Do not change the first line — it is a po4a header that specifies the insertion point.
Add a line such as:
Translated by Your Name <your@email.example>
Use an empty line between paragraphs. Two consecutive non-empty lines are printed without a line break between them.
Verifying the Translated Pages
The rendered pages are produced by the build, not committed to git. To preview your translation, build aMule with po4a installed — translated man pages are rendered by default when ENABLE_NLS=YES (the default):
cmake -B build
cmake --build build
Then view the rendered pages from the build directory:
man build/docs/man/amule.pt_BR.1
man build/docs/man/amuled.pt_BR.1
man build/docs/man/amulecmd.pt_BR.1
man build/docs/man/amulegui.pt_BR.1
man build/docs/man/amuleweb.pt_BR.1
man build/docs/man/ed2k.pt_BR.1
man build/src/utils/aLinkCreator/docs/alc.pt_BR.1
man build/src/utils/aLinkCreator/docs/alcc.pt_BR.1
man build/src/utils/cas/docs/cas.pt_BR.1
man build/src/utils/wxCas/docs/wxcas.pt_BR.1
Build Integration
When ENABLE_NLS=YES (the default) and po4a is found, the build renders every translated man page from the English masters and the .po files into the build directory and installs the result under <prefix>/share/man/<lang>/man1/. If po4a is missing, the build skips the translated man pages and installs only the English ones — pass -DTRANSLATED_MANPAGES=NO to opt out explicitly. See Translated Man Pages in the Compilation guide for the build options.
Packagers who want to avoid po4a as a build-time dependency can build from the source bundle attached to each GitHub Release (aMule-<VERSION>-src.tar.gz) instead of the GitHub-generated "Source code" archive — it ships the pre-rendered *.<lang>.1.in files alongside the English masters.
Submitting a Man Page Translation
Open a pull request with:
docs/man/po/manpages-<lang>.po— the translation file.docs/man/po/manpages-<lang>.add— the addendum (optional but encouraged).- For a new language, the updated
docs/man/po4a.config.in.
Do not include rendered *.<lang>.1.in pages — they are generated at build time and are no longer tracked in git.
Documentation Translations
The website documentation (this site) is internationalized through Docusaurus's i18n system. Translations cover two areas: UI strings (navbar, sidebar labels, homepage text) stored in i18n/<locale>/code.json, and documentation pages stored as Markdown files under i18n/<locale>/docusaurus-plugin-content-docs/current/.
The step-by-step instructions for adding or updating a documentation translation live in the Documentation guide, alongside the rest of the documentation workflow. The website translations are managed on Weblate — see the Weblate guide for the base files, components, and workflow.