Translations
aMule has three areas that can be translated: the application interface strings, the man pages, and the website documentation.
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.
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"
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 is the canonical list.
File Layout
docs/man/
├── amule.1 # English man page (master)
├── amuled.1
├── amulecmd.1
├── amulegui.1
├── amuleweb.1
├── ed2k.1
├── po4a.config # po4a configuration
├── amule.de.1 # Generated translated man pages
├── amule.es.1
└── 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 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/). Their translated pages (alc.<lang>.1, …) are generated in those same directories. 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.
Starting a New Man Page Translation
If po4a is installed:
cd docs/man
# Add your language code to the [po4a_langs] line in po4a.config (alphabetically).
# Current line: "de es fr hu it pt_BR ro ru tr zh_TW"
# Example: to add Dutch, change it to "de es fr hu it nl pt_BR ro ru tr zh_TW"
po4a po4a.config
# Your new .po file is now in po/manpages-nl.po
If po4a is not installed:
cd docs/man/po
cp manpages.pot manpages-nl.po
# Start translating. Mention in the PR that you didn't use po4a.
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.
Generating and Verifying the Translated Pages
With po4a installed, after finishing your .po file:
cd docs/man
po4a po4a.config
Check the generated pages:
man docs/man/amule.pt_BR.1
man docs/man/amuled.pt_BR.1
man docs/man/amulecmd.pt_BR.1
man docs/man/amulegui.pt_BR.1
man docs/man/amuleweb.pt_BR.1
man docs/man/ed2k.pt_BR.1
Regenerating with CMake
Maintainers can regenerate all translated man pages from within the build system (requires po4a at configure time):
cmake --build build --target po4a-update
This rewrites docs/man/po/manpages.pot, syncs each manpages-LANG.po, and regenerates the translated *.LANG.1 files. Commit the resulting changes.
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).- The generated
*.LANG.1man pages (if po4a is installed and they look correct).
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/.
Full instructions for adding or updating a documentation translation are in Contributing to Documentation.