Weblate
aMule translations live in git — the repository is the source of truth. Weblate is a translation tool connected to the repositories: it reads the English source strings from git and commits translations back to it, so changes flow in both directions. Weblate is organized into components, one per translatable file in git. This page documents how they are set up and how a translation travels from Weblate back to the repository.
Using Weblate is optional: translations can also be contributed by opening a pull request to the relevant repository. Both options are equally valid and edit the same files — see the Translations guide for the manual workflow.
Weblate components follow one of two models, matching how each part of the project stores its translations. The application and man-page components are bilingual: each gettext .po file contains both the English source string and its translation, and the .pot template lists the source strings. The Web UI and website components are monolingual: an English base file lists the source strings, plus one translation file per language.
The components span two repositories — the application, man pages and Web UI are in amule-org/amule, and the website is in amule-org/amule-org.github.io — and are grouped in the sections below by the part of the project they cover:
- aMule application — the application interface strings, including the Windows installer strings.
- Man pages — the command-line manual pages.
- Web UI — the browser interface served by
amuleapi. - Website — the project website (UI strings only; the documentation is English-only).
How Weblate is organized
Everything lives in a single Weblate project, aMule, which holds one component per translatable file in the repositories. Each component keeps one translation per language enabled on it, so a language exists in a component only after it has been added there. Components can therefore support different sets of languages — in practice a new language is enabled on every component at once, but nothing enforces it, and a component may lag behind the others.
Adding a language is an administrator action. Translators cannot create one: Weblate's Start new translation button sends a request to the maintainers instead (this is the Contact maintainers setting described below), because the locale must also be registered in the repository before the translation files make sense there. An administrator handles both steps (see Adding a language for the Web UI and the website).
Translating is open to everyone; approval is not. Any Weblate account can edit any string of any existing language that is not yet approved (approved strings can only be changed by reviewers; others can add suggestions), but what it saves is stored as waiting for review. Only administrators and members of the Review team can approve it, and only approved translations are written to the files and included in Weblate's pull requests (see the workflow settings below). The contributor-facing explanation — translation states, Save versus Suggest, and how to become a reviewer — is in Translating aMule.
End to end:
- A translator edits a string in Weblate. It is saved as waiting for review.
- A reviewer for that language (or an administrator) approves it.
- An administrator commits and pushes from aMule → Operations → Repository maintenance; see Synchronizing with git.
- Weblate opens a pull request containing the approved translations only.
- The maintainers merge it, and the translation ships with the next website or application build.
For Weblate administrators
Set up one Weblate component per file in the git repository. Every component must be configured with:
- Settings → Version control → Version control system: GitHub pull request — Weblate proposes its changes as pull requests instead of committing directly to the branch.
- Settings → Files → File format:
- gettext PO file for the application and man-page catalogs. This is the bilingual variant — do not use gettext PO file (monolingual), which does not match how aMule's
.pofiles are laid out. - i18next JSON file v4 for the Web UI dictionaries (see Web UI).
- WebExtension JSON file for the Docusaurus JSON files. Docusaurus stores each entry as
{"key": {"message": "…", "description": "…"}}, which is exactly the WebExtensionmessages.jsonshape: Weblate translates themessagevalue and usesdescriptionas the source context.
- gettext PO file for the application and man-page catalogs. This is the bilingual variant — do not use gettext PO file (monolingual), which does not match how aMule's
- Workspace → Settings → Adding new translation: Contact maintainers — translators cannot create new languages directly; they request them from the maintainers, who register the locale in the repository first.
For each JSON component, set Settings → Files → JSON indentation to 2, matching the 2-space indentation of the files in git so Weblate does not reformat them (Weblate defaults to 4).
For each gettext component, set Settings → Files → Long lines wrapping to Only wrap lines at newlines (like 'xgettext --no-wrap') — the .po files in git are stored unwrapped, and any other value would make Weblate re-wrap every file it touches.
At the project level, enable the Remove blank strings add-on. It removes untranslated (blank) strings from the translation files, so the Docusaurus JSON files never end up with empty message values and the Web UI dictionaries only keep the blank forms of partly translated plurals (see Web UI).
Also enable the Squash Git commits add-on, with Commit squashing set to All commits into one and Append trailers to squashed commit message enabled. This keeps each Weblate pull request to a single, clean commit while preserving the per-translator attribution trailers.
Also at the project level, under aMule → Settings → Workflow:
- Enable reviews is turned on: anyone on Weblate can change a translation that is not yet approved (approved strings can only be changed by reviewers), but the change stays waiting for review until a reviewer or an administrator approves it.
- Translation quality filter is set to Only include approved translations: only reviewer-approved translations are written to the files and included in Weblate's pull requests.
Adding reviewers
Contributors ask to become reviewers by opening an issue, as described in Becoming a Reviewer. Reviewers are added in aMule → Operations → Users → Teams → Review. A reviewer can be global (able to approve every language) or restricted to specific languages. Prefer restricting each reviewer to specific languages: Weblate then lists them in the UI for those languages, so contributors can see and contact the reviewer responsible for a given language.
Synchronizing with git
Weblate synchronizes with the Git repository in both directions from aMule → Operations → Repository maintenance:
- Pushing to git. By default Weblate does not commit to Git on its own. First press Commit to commit the pending changes — only approved translations are included in the commit; entries listed as skipped are not yet approved. Then press Push to make Weblate open a pull request with the committed translations.
- Pulling from git. Weblate detects changes in Git automatically. To pull manually, press Update. If Weblate and the repository ever get out of sync, press Reset and reapply to discard Weblate's local state and re-pull from Git — this is the last resort.
The sections below list the file masks and base files for each component.
aMule application
The aMule interface strings are managed with GNU gettext (.po files in po/). See Code Translations for the manual workflow. The same .po catalogs also contain the Windows installer strings, so they are covered by this component.
| Component name | File mask | Template | Format |
|---|---|---|---|
Application | po/*.po | po/amule.pot | gettext PO file |
The template po/amule.pot is regenerated in git by ./scripts/update-po.sh, which also merges the new and changed strings into every .po file; Weblate imports the result when it pulls from git.
Man pages
The man pages are translated with po4a (.po files in docs/man/po/). See Man Page Translations for the manual workflow.
| Component name | File mask | Template | Format |
|---|---|---|---|
Application Man Pages | docs/man/po/manpages-*.po | docs/man/po/manpages.pot | gettext PO file |
The template docs/man/po/manpages.pot is regenerated in git by ./scripts/update-manpages-po.sh, which also merges the new and changed strings into every manpages-*.po file; Weblate imports the result when it pulls from git. The po4a addenda (docs/man/po/manpages-*.add, the translator-credit blocks) are not managed by Weblate — they are edited directly in git.
The rendered translated man pages are not tracked in git — the build renders them from the .po files at build time — so a Weblate pull request touching only the manpages-*.po files is complete by itself.
Web UI
The amuleapi Web UI loads its strings from flat key → string JSON dictionaries in src/webapi/static/i18n/, with en.json as the English source. See Web UI Translations for the manual workflow.
| Component name | File mask | Monolingual base file | Format |
|---|---|---|---|
Application WebUI | src/webapi/static/i18n/*.json | src/webapi/static/i18n/en.json | i18next JSON file v4 |
Besides the common settings above, set Settings → Translation → Translation flags to placeholders:r"\{[a-z_]+\}". The Web UI uses {name} placeholders, not i18next's {{name}}, so this flag is what makes Weblate highlight them and check that each translation keeps them. Leave Template for new translations empty: a new language then starts as an empty file instead of one with every key blank. Leave Language code style at its default, which names the files with BCP 47 codes such as pt-BR: the Web UI passes the code to the browser's Intl APIs, which reject POSIX codes such as pt_BR. For the same reason, pick the current code when Weblate offers a deprecated one — fil, not tl; sr-Latn, not sh — as the CI check rejects codes that Intl replaces with an alias.
The i18next JSON file v4 format is used for its plural handling: the Web UI resolves plurals as <key>_one / <key>_other with the CLDR plural rules, the same suffixes i18next v4 uses. Weblate therefore groups each pair as one plural string and offers the extra forms a language needs (_few, _many, …). Note that some languages, e.g. Spanish, French, Italian, Portuguese and Catalan, also have a _many form, used for large round numbers such as 1 000 000, so their existing plurals show up in Weblate with that form missing.
Translation files may lack keys or hold blank ones — untranslated and unapproved strings are not written, and Weblate writes the untranslated forms of a partly translated plural as "". The Web UI falls back per key to English, and a plural form English does not have (_few, _many, …) to the translation's _other. The i18n CI workflow (src/webapi/tools/check-i18n.mjs) rejects translation keys that are not in en.json, translations whose {placeholders} differ from the English source (plural forms may drop {n}, so Weblate's placeholder warning on such a form can be dismissed), and file names that are not canonical BCP 47 codes.
The format is hierarchical, so en.json keys must not contain . or [] (Weblate would nest them), and a _one/_other… suffix is reserved for plural pairs. The CI check enforces both.
Adding a language
- Add the language in Weblate to the
Application WebUIcomponent. Weblate createssrc/webapi/static/i18n/<code>.json, with a BCP 47 code such aspt-BR, and proposes it in its next pull request. - Register the language by adding the same code to
LANGSinsrc/webapi/static/js/i18n.js. Until then the file is kept in sync by Weblate but the Web UI does not offer it. The Web UI picks it from the browser's preferred languages, in order, by exact code and then by language and script (azh-CNbrowser getszh-Hans, azh-TWonezh-Hant).
Website
Only the website's UI strings (React components, navbar, footer, blog and changelog titles and descriptions) are translated in Weblate. The documentation — including its sidebar — is English-only: do not add files under i18n/<locale>/docusaurus-plugin-content-docs/, as the docs plugin would still serve them.
The English UI strings live in code (<Translate> / translate() defaults) and in docusaurus.config.ts, not in a JSON file, so the base files are generated into i18n/en/ with:
npm run write-translations
This writes i18n/en/code.json and the matching docusaurus-theme-classic/ and blog/changelog JSON files. These files are committed so Weblate has a stable base to read from. They are safe for the site — Docusaurus uses them for the en locale and their values equal the in-code defaults, so the rendered output is unchanged.
The i18n/en/ base files must stay in sync with the source strings. After adding or changing any <Translate> string, regenerate them with npm run write-translations and commit the result. The Build Check CI workflow runs the same command and fails the pull request if i18n/en/ is out of date.
Components
Configure one Weblate component per source file. All component names are prefixed Website: so this site's components stay grouped within the Weblate project.
| Component name | File mask | Monolingual base file | Format |
|---|---|---|---|
Website: Web | i18n/*/code.json | i18n/en/code.json | WebExtension JSON file |
Website: Navbar | i18n/*/docusaurus-theme-classic/navbar.json | i18n/en/docusaurus-theme-classic/navbar.json | WebExtension JSON file |
Website: Footer | i18n/*/docusaurus-theme-classic/footer.json | i18n/en/docusaurus-theme-classic/footer.json | WebExtension JSON file |
Website: Blog Sidebar | i18n/*/docusaurus-plugin-content-blog/options.json | i18n/en/docusaurus-plugin-content-blog/options.json | WebExtension JSON file |
Website: Changelog Sidebar | i18n/*/docusaurus-plugin-content-blog-changelog/options.json | i18n/en/docusaurus-plugin-content-blog-changelog/options.json | WebExtension JSON file |
Each Docusaurus JSON entry is an object ({"message": "...", "description": "..."}), which matches the WebExtension JSON file format: Weblate translates the message value and shows description as its context.
Conventions
- Descriptions are context, not translated. With the WebExtension JSON format, each
descriptionis presented to translators as the source string's context and is never written into the locale files (which only containmessage). - Git stays editable. Components track the repository in both directions: Weblate proposes translations via GitHub pull requests, and changes committed to git are imported into Weblate.
Adding a language
A website language has two parts: its translation files under i18n/<code>/, managed by Weblate, and its registration in docusaurus.config.ts, which makes Docusaurus build and publish it. Locales present in i18n/ but not registered are kept in sync by Weblate but are not built.
-
Add the language in Weblate to every
Website:component. Weblate creates the translation files underi18n/<code>/and proposes them in its next pull request. -
Register the locale in
docusaurus.config.ts, ini18n.localesandi18n.localeConfigs, with the language's own name aslabel:i18n: {defaultLocale: 'en',locales: ['en', /* … */ 'de'], // add your locale code herelocaleConfigs: {// …de: {label: 'Deutsch'},},},The locale code is used in the URL (
/de/…) and must match the folder name underi18n/. If Weblate's folder name is not a valid BCP 47 tag (e.g.zh_Hans), use a valid code as the locale and point it at the folder withpathandhtmlLang, e.g.'zh-Hans': {label: '简体中文', path: 'zh_Hans', htmlLang: 'zh-Hans'}.Do not add the locale to the
languagearray of the@easyops-cn/docusaurus-search-localtheme: the documentation is English-only, so the search index uses English in every locale. -
Fill in missing keys (only needed when the files were not created by Weblate, or are outdated):
npm run write-translations -- --locale <code>This adds any missing keys and preserves existing ones. It also writes
descriptionfields, which must be removed from the locale files before committing — only the English base files ini18n/en/contain them. -
Build and preview the new locale:
npm run build -- --locale <code>npm run start -- --locale <code>