Translations
njMule's interface speaks 36 languages, in the desktop window and in the web interface alike. Most of its words come from eMule's own translations, converted so that the interface keeps eMule's familiar wording; the texts njMule adds are translated in the same terms. This page explains where the texts live, how to correct one, and everything a new language needs.
The languages
English, Catalan, Czech, Danish, German, Estonian, Spanish, French, Galician, Italian, Latvian, Lithuanian, Hungarian, Dutch, Norwegian Bokmål, Polish, Brazilian and European Portuguese, Romanian, Albanian, Slovenian, Finnish, Swedish, Vietnamese, Turkish, Greek, Bulgarian, Russian, Ukrainian, Arabic, Persian, Hebrew, Simplified and Traditional Chinese, Japanese and Korean.
Arabic, Persian and Hebrew are written right to left: the page is mirrored (dir="rtl"), while
paths, links, addresses and sizes keep left to right, and names keep their own direction.
The interface speaks the language chosen in its options, kept for the next windows; a web address
may also ask for one with ?lang=<tag>, such as ?lang=it. Otherwise it takes the best match of
the system's languages (the browser's for the web interface): the language with its region, then the
language alone, then its first regional variant (pt gets Brazilian Portuguese), and English when
none matches.
Where the texts live
All texts are Java properties files in UTF-8, in
njmule-ui/i18n/. Each language has
two files; <lang> is a language code, with the region for the two Portuguese and the two Chinese
(pt_BR, pt_PT, zh_CN, zh_TW):
| File | Keys | What it holds |
|---|---|---|
emule.properties, emule_<lang>.properties | IDS_* | eMule's interface strings and their translations, generated from the string tables of eMule's resource scripts by tools/Rc2Properties.java. Never edit them by hand: regenerate them |
nj.properties, nj_<lang>.properties | nj.*, and IDS_* in translations | njMule's own texts, in English and in each language; a translation may also reword some of eMule's texts in that language |
The files without a suffix are English, which is also the fallback: a text missing in a language
shows in English. The interface looks a text up by its key, t('IDS_DL_FILENAME') or
t('nj.wizard.stepOf', step, steps); an unknown key shows itself in brackets, [nj.some.key].
The build converts the files into one JSON file per language, <tag>.json with a hyphen in the
tag (pt-BR), holding only the keys the interface's sources name
(Interface).
For an IDS_* key, the language's nj_<lang>.properties wins over its emule_<lang>.properties
when it holds the key: that is how a translation rewords one of eMule's texts. The rewordings sit at
the end of each nj_<lang>.properties, under a comment that says why that language rewords them
(for example, Italian keeps "upload" and "download" in English, as Italian software usually does).
The English nj.properties never holds IDS_* keys.
Regenerating eMule's texts
Rc2Properties reads eMule's resource scripts (the English one, then one per language), keeps the
IDS_* string tables, drops the menu mnemonics (&O, and (&O) in Chinese, Japanese and Korean)
and turns C format specifiers into Java ones:
java tools/Rc2Properties.java <english .rc> njmule-ui/i18n <lang>=<translated .rc>...
It writes emule.properties and one emule_<lang>.properties per language given, keeping only the
keys the English file has, and prints which strings each language lacks.
Fixing a text
- Find the key: search the English bundles for the text as it appears.
- Choose the file:
- an
nj.*text, in any language: edit it innj.properties(English) or innj_<lang>.properties; - an
IDS_*text in one language: add the key with the new wording to that language'snj_<lang>.properties, among the rewordings at its end; never editemule_<lang>.properties; - an
IDS_*text in English: the English eMule texts are not reworded. Give the code a newnj.*key with the better text instead, translated in every language.
- an
- Keep the placeholders (see Placeholders).
- Look at it in the interface (Interface), in its
language: choose it in the options, or add
?lang=<tag>to the page's address. - Commit with the scope
i18n, for examplefix(i18n): word the upload counter as the download one in German.
A new text
A new nj.* key goes into nj.properties and every nj_<lang>.properties: each translation
holds exactly the English set of nj.* keys, none blank. Write the key literally in the interface's
sources, in quotes: the build keeps only the keys it finds there, so a key built at run time
(t('nj.mode.' + mode)) must also be written in full somewhere in the sources, as the tables of
settings.ts and network.ts do.
Placeholders
The texts use Java's format specifiers, which format() in njmule-ui/src/i18n.ts fills in:
| Specifier | Meaning |
|---|---|
%s, %d | a text, a whole number |
%.1f | a number with one decimal |
%1$s, %2$d | an argument by its position, to change their order |
%% | a percent sign |
A translation must keep, for every argument, the same conversion as the English text: it may move
them around with positions (%2$s … %1$s), but not drop, add or change one. Whole numbers are
formatted with the interface language's separators.
Adding a language
A new language needs eMule's translation of its texts, so that emule_<lang>.properties exists.
Then:
- Convert eMule's translation with
Rc2Properties, adding<lang>=<translated .rc>to the command above. - Translate
nj.propertiesinto a newnj_<lang>.properties, every key included, with the SPDX header of the other files. - Offer it: add its tag (
pt-BRstyle, with a hyphen) toLANGUAGESinnjmule-ui/src/i18n.ts, and its own name, as its speakers write it ("Deutsch", "Italiano"), toLANGUAGE_NAMESinnjmule-ui/src/settings.ts, which the language boxes show. Keep both lists in the same order: English first, then the languages by their own names, as the lists are. - Right to left? Add its code to the list in
loadLanguage()ofi18n.ts, which sets the page's direction. - Update the documents: the count of languages in the
README.md, on the home page (website/src/pages/index.tsx) and in the user guide (search its pages for it), the list of languages on this page and in the user guide's Appearance and language page, and a line inCHANGELOG.md.
The rest follows by itself: the build makes a JSON file for every nj_<lang>.properties, with that
language's emule_<lang>.properties beneath it.
The tests that guard them
The interface's Vitest tests check how the texts are used (Testing):
| Test | What it checks |
|---|---|
i18n.test.ts | placeholders are filled in, by order and by position, and decimals rounded; the browser's languages pick a translation, a regional variant or English |
language.test.ts | a language chosen in the options shows at once and is kept for the next windows; with none chosen, the best match of the system's languages shows |
No test checks the bundles themselves: keep the keys and the placeholders of every translation in step with English by hand.
Related pages
- Interface: how the interface uses the keys.
- Testing: running the tests.
- Contributing: commit messages and pull requests.