docs: Explain web app translations before server, and use those terms.

This seems good for prioritizing what is important first, and should
make it easier to add the Flutter app in the future.
This commit is contained in:
Tim Abbott
2025-01-31 13:35:23 -08:00
parent e0cf9d33b8
commit bd08c0b980
2 changed files with 96 additions and 91 deletions

View File

@@ -73,92 +73,15 @@ A few general notes:
extract the strings in a project to send to translators will not
find your string.
- Zulip makes use of the [Jinja2][] templating system for the backend
and [Handlebars][] for the frontend. Our [HTML templates][html-templates]
- Zulip makes use of the [Jinja2][] templating system for the server
and [Handlebars][] for the web app. Our [HTML templates][html-templates]
documentation includes useful information on the syntax and
behavior of these systems.
### Backend translations
### Web application translations
#### Jinja2 templates
All user-facing text in the Zulip UI should be generated by an Jinja2 HTML
template so that it can be translated.
To mark a string for translation in a Jinja2 template, you
can use the `_()` function in the templates like this:
```jinja
{{ _("English text") }}
```
If a piece of text contains both a literal string component and variables, use a
block translation. This puts in placeholders for variables, to allow translators
to translate an entire sentence.
To tag a block for translation, Jinja2 uses the [trans][trans] tag, like this:
```jinja
{% trans %}This string will have {{ value }} inside.{% endtrans %}
```
Never break up a sentence like this, as it will make it impossible to translate
correctly:
```jinja
# Don't do this!
{{ _("This string will have") }} {{ value }} {{ _("inside") }}
```
#### Python
A string in Python can be marked for translation using the `_()` function,
which can be imported as follows:
```python
from django.utils.translation import gettext as _
```
Zulip expects all the error messages to be translatable as well. To
ensure this, the error message passed to `JsonableError`
should always be a literal string enclosed by `_()`
function, for example:
```python
JsonableError(_('English text'))
```
If you're declaring a user-facing string at top level or in a class, you need to
use `gettext_lazy` instead, to ensure that the translation happens at
request-processing time when Django knows what language to use, for example:
```python
from zproject.backends import check_password_strength, email_belongs_to_ldap
AVATAR_CHANGES_DISABLED_ERROR = gettext_lazy("Avatar changes are disabled in this organization.")
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse:
...
```
```python
class Realm(models.Model):
MAX_REALM_NAME_LENGTH = 40
MAX_REALM_SUBDOMAIN_LENGTH = 40
...
...
STREAM_EVENTS_NOTIFICATION_TOPIC = gettext_lazy("channel events")
```
To ensure we always internationalize our JSON error messages, the
Zulip linter (`tools/lint`) attempts to verify correct usage.
### Frontend translations
We use the [FormatJS][] library for frontend translations when dealing
with [Handlebars][] templates or JavaScript.
We use the [FormatJS][] library for translations in the Zulip web app,
both in [Handlebars][] templates and JavaScript.
To mark a string translatable in JavaScript files, pass it to the
`intl.formatMessage` function, which we alias to `$t` in `intl.js`:
@@ -258,19 +181,102 @@ custom HTML tag like this:
{{/tr}}
```
### Server translations
Strings in the server primarily comprise two areas:
- Error strings and other values returned by the API.
- Strings in portico pages, such as the login flow, that are not
rendered using JavaScript or Handlebars.
#### Jinja2 templates
All user-facing text in the Zulip UI should be generated by an Jinja2 HTML
template so that it can be translated.
To mark a string for translation in a Jinja2 template, you
can use the `_()` function in the templates like this:
```jinja
{{ _("English text") }}
```
If a piece of text contains both a literal string component and variables, use a
block translation. This puts in placeholders for variables, to allow translators
to translate an entire sentence.
To tag a block for translation, Jinja2 uses the [trans][trans] tag, like this:
```jinja
{% trans %}This string will have {{ value }} inside.{% endtrans %}
```
Never break up a sentence like this, as it will make it impossible to translate
correctly:
```jinja
# Don't do this!
{{ _("This string will have") }} {{ value }} {{ _("inside") }}
```
#### Python
A string in Python can be marked for translation using the `_()` function,
which can be imported as follows:
```python
from django.utils.translation import gettext as _
```
Zulip expects all the error messages to be translatable as well. To
ensure this, the error message passed to `JsonableError`
should always be a literal string enclosed by `_()`
function, for example:
```python
JsonableError(_('English text'))
```
If you're declaring a user-facing string at top level or in a class, you need to
use `gettext_lazy` instead, to ensure that the translation happens at
request-processing time when Django knows what language to use, for example:
```python
from zproject.backends import check_password_strength, email_belongs_to_ldap
AVATAR_CHANGES_DISABLED_ERROR = gettext_lazy("Avatar changes are disabled in this organization.")
def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpResponse:
...
```
```python
class Realm(models.Model):
MAX_REALM_NAME_LENGTH = 40
MAX_REALM_SUBDOMAIN_LENGTH = 40
...
...
STREAM_EVENTS_NOTIFICATION_TOPIC = gettext_lazy("channel events")
```
To ensure we always internationalize our JSON error messages, the
Zulip linter (`tools/lint`) attempts to verify correct usage.
## Translation process
The end-to-end tooling process for translations in Zulip is as follows.
1. The strings are marked for translation (see sections for
[backend](#backend-translations) and
[frontend](#frontend-translations) translations for details on
[server](#server-translations) and
[web app](#web-application-translations) translations for details on
this).
2. Translation resource files are created using the
`./manage.py makemessages` command. This command will create, for
each language, a resource file called `translations.json` for the
frontend strings and `django.po` for the backend strings.
web app strings and `django.po` for the server strings.
The `makemessages` command is idempotent in that:
@@ -304,11 +310,10 @@ sense of how everything fits together.
## Translation resource files
All the translation magic happens through resource files, which hold
the translated text. Backend resource files are located at
`locale/<lang_code>/LC_MESSAGES/django.po`, while frontend
resource files are located at
`locale/<lang_code>/translations.json` (and mobile at
`mobile.json`).
the translated text. Server resource files are located at
`locale/<lang_code>/LC_MESSAGES/django.po`, while web app resource
files are located at `locale/<lang_code>/translations.json` (and
mobile at `mobile.json`).
These files are uploaded to [Transifex][], where they can be translated.

View File

@@ -43,7 +43,7 @@ if __name__ == "__main__":
print(
WARNING
+ "See https://zulip.readthedocs.io/en/latest/translating/internationalization.html#frontend-translations "
+ "See https://zulip.readthedocs.io/en/latest/translating/internationalization.html#web-application-translations "
"on how you can insert variables in the frontend translatable "
"strings." + ENDC
)