diff --git a/docs/translating/internationalization.md b/docs/translating/internationalization.md index 21f3c12874..97aa7165c5 100644 --- a/docs/translating/internationalization.md +++ b/docs/translating/internationalization.md @@ -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//LC_MESSAGES/django.po`, while frontend -resource files are located at -`locale//translations.json` (and mobile at -`mobile.json`). +the translated text. Server resource files are located at +`locale//LC_MESSAGES/django.po`, while web app resource +files are located at `locale//translations.json` (and +mobile at `mobile.json`). These files are uploaded to [Transifex][], where they can be translated. diff --git a/tools/check-frontend-i18n b/tools/check-frontend-i18n index 5656a3584c..ce61c20210 100755 --- a/tools/check-frontend-i18n +++ b/tools/check-frontend-i18n @@ -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 )