Translations
The panel has catalogs for English, Dutch (nl), German (de) and French
(fr). It shows the language that the browser asks for, until the person
selects a different language in the language list in the site header. The list
is a plain form that posts to the set_language view of Django, and it needs no
account and no script. The view stores the choice in a cookie.
The translations are gettext catalogs in the repository, one directory for each language:
panel/src/panel/locale/<language>/LC_MESSAGES/django.po
panel/src/panel/locale/<language>/LC_MESSAGES/djangojs.po
djangojs.po holds the strings of the JavaScript files in static/, and
django.po holds all other strings.
The repository keeps only the .po files. The Nix package compiles them into
the .mo files that Django reads, and the dev shell compiles them when it
starts. Git ignores the .mo files.
A machine made the first Dutch, German and French translations. A native speaker must review them.
Add or change a string
Write the string in English in the source, and mark it for translation:
- Templates:
{% load i18n %}, then{% translate "Save" %}. For a string with values or HTML, use{% blocktranslate trimmed with name=object.name %}Configure {{ name }}{% endblocktranslate %}. Keep values that are not prose, for example a URL or a JSON example, outside of the string. Ablocktranslateblock cannot hold a different block tag: give a{% url %}value a name withasbefore the block. - Inline scripts:
{{ _("Saved.")|escapejs }}. For a value, write{name}in the string, and give the value towindow.panelNotice.fillin the script. - JavaScript files in
static/: the global functiongettext("Saved."). For a value, useinterpolate(gettext("Open %(name)s."), { name: value }, true).base.htmlloads these functions from thejavascript_catalogview before the scripts. A value that goes into HTML must be escaped first. - The script of the deployment form (
panel/forms-island-adapter): adata-label-*attribute on the<body>ofbase.html, with a{% translate %}value. The translation must not have a double quote. - Python:
gettextfromdjango.utils.translationin views, andgettext_lazyfor strings that Python evaluates at import time, for example form labels and choices.
The test panel/tests/test_unmarked_strings.py fails on text without a mark in
the places where text goes to the user: the text and the visible attributes of a
template, the output calls of JavaScript and HTML in a JavaScript string, and in
Python the messages, the validation errors, the mails, and the labels and help
texts. It does not see text that goes to the user through a variable. Text that
must stay in English goes in NOT_TRANSLATED in that test, with its reason.
A management command has no request and thus no language of a person, so its mails are in English.
Then update the catalogs from the dev shell:
panel-makemessages
This adds the new strings to each .po file with an empty translation, and
removes the strings that the source no longer has. Fill in the msgstr of each
new string, and commit the .po files. When a string changes,
panel-makemessages can keep the translation of a similar string and add a
#, fuzzy line. Examine that translation and remove the line. A string with an
empty msgstr or a #, fuzzy line shows in English.
Each translation must keep the placeholders (%(name)s) and the HTML tags of
its English string.
The check panel-translations fails when the .po files are not the same as
the output of panel-makemessages, and when an entry has an empty msgstr or
a #, fuzzy line. Thus, run panel-makemessages after each change to a marked
string, and fill in the translations of each language. The check ignores the line wrap, the
POT-Creation-Date and the references to the source. It sees only marked
strings: it does not find a string that is not marked yet.
panel-makemessages writes the files with --no-wrap: it breaks a line only
at a newline in a string. A tool that edits the files must write them with the
same setting, so that its changes do not wrap the lines again. In Weblate, set
the file format parameter "Long lines wrapping" (po_line_wrap) of the
component to "Only wrap lines at newlines".
Add a language
- Add the language code and its name, in that language, to
LANGUAGESinpanel/src/panel/settings.py. - Make its directory:
mkdir -p panel/src/panel/locale/<language>/LC_MESSAGES. - Run
panel-makemessages. This writes the two.pofiles of the language. - In the header of each file, set
Language,Language-Teamand thePlural-Formsof the language, and remove the#, fuzzyline abovemsgid "". - Translate the strings, and commit the files.
The tests test_each_language_has_a_compiled_catalog and
test_each_language_has_a_script_catalog fail for a language in LANGUAGES
that has no catalog.
What is not translated
The templates, the JavaScript files in static/, and the strings of the views
and forms that a person reads, are marked for translation. The deployment
service sends the titles, descriptions and confirmations of its actions, and the
reason when it disables an action, in English. The file
api/src/fediversity_api/procedures.py marks these strings with gettext_noop,
panel-makemessages reads that file, and the panel translates the text when it
shows the text. Thus the check panel-translations also fails when one of these
strings changes and the catalogs do not. These strings show in
English:
- The strings of the deployment form (
nix-module-form). - The labels and descriptions of the deployment options, and the text of the
operations of an application. They come from the NixOS modules in
core/. - The error that the deployment service sends when it refuses a request, and the results in the history of an account.
- The strings of the administration interface, for example the help text of a model.
- The strings that no person reads in the panel: the answers to the callbacks of Centrifugo and of the deployment service, and the log lines.