2016-04-29 06:55:09 +02:00
|
|
|
# Translating Zulip
|
|
|
|
|
2016-10-19 14:51:20 +02:00
|
|
|
Zulip has full support for Unicode, so you can already use your
|
2016-04-29 06:55:09 +02:00
|
|
|
preferred language everywhere in Zulip.
|
|
|
|
|
|
|
|
To make Zulip even better for users around the world, the Zulip UI is
|
|
|
|
being translated into a number of major languages, including Spanish,
|
|
|
|
German, French, Chinese, Russian, and Japanese, with varying levels of
|
|
|
|
progress. If you speak a language other than English, your help with
|
|
|
|
translating Zulip would be greatly appreciated!
|
|
|
|
|
2016-10-19 14:50:23 +02:00
|
|
|
If you're interested in contributing translations to Zulip, please
|
|
|
|
[join the "translation" stream in our developers' Zulip
|
2016-10-26 10:12:32 +02:00
|
|
|
chat](https://chat.zulip.org/#narrow/stream/translation), and say
|
2016-10-19 14:50:23 +02:00
|
|
|
hello. And please join the [Zulip project on
|
|
|
|
Transifex](https://www.transifex.com/zulip/zulip/) and ask to join any
|
|
|
|
languages you'd like to contribute to (or add new ones). Transifex's
|
|
|
|
notification system sometimes fails to notify the maintainers when you
|
|
|
|
ask to join a project, so please send a quick email to
|
|
|
|
zulip-core@googlegroups.com when you request to join the project or
|
|
|
|
add a language so that we can be sure to accept your request to
|
|
|
|
contribute.
|
2016-05-06 13:22:37 +02:00
|
|
|
|
2016-06-27 10:32:15 +02:00
|
|
|
## Setting Default Language in Zulip
|
2016-06-30 13:56:04 +02:00
|
|
|
|
2016-06-27 10:32:15 +02:00
|
|
|
Zulip allows you to set the default language through the settings
|
2016-10-19 14:51:20 +02:00
|
|
|
page, in the 'Display Settings' section. The URL will be
|
|
|
|
`/#settings/display-settings` on your realm.
|
2016-06-27 10:32:15 +02:00
|
|
|
|
2016-06-30 13:56:04 +02:00
|
|
|
## Translation Resource Files
|
|
|
|
|
|
|
|
All the translation magic happens through resource files which hold
|
|
|
|
the translated text. Backend resource files are located at
|
|
|
|
`static/locale/<lang_code>/LC_MESSAGES/django.po`, while frontend
|
2016-07-29 23:49:51 +02:00
|
|
|
resource files are located at
|
|
|
|
`static/locale/<lang_code>/translations.json`. These files are
|
|
|
|
uploaded to Transifex using `tx push`, where they can be
|
|
|
|
translated. Once translated, they are downloaded back into the
|
|
|
|
codebase using `tx pull`.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
|
|
|
## Transifex Config
|
|
|
|
|
|
|
|
The config file that maps the resources from Zulip to Transifex is
|
|
|
|
located at `.tx/config`. Django recognizes `zh_CN` instead of `zh-HANS`
|
|
|
|
for simplified Chinese language (this is fixed in Django 1.9). This
|
|
|
|
idiosyncrasy is also handled in the Transifex config file.
|
|
|
|
|
|
|
|
## Translation Process
|
|
|
|
|
|
|
|
The end-to-end process to get the translations working is as follows:
|
|
|
|
|
2016-10-19 14:51:20 +02:00
|
|
|
1. Mark the strings for translations (see sections for
|
|
|
|
[backend](#backend-translations) and
|
|
|
|
[frontend](#frontend-translations) translations for details on
|
|
|
|
this).
|
|
|
|
|
2016-10-20 00:34:31 +02:00
|
|
|
2. Create translation [resource][] files using the `python manage
|
2016-10-20 00:32:28 +02:00
|
|
|
makemessages` command. This command will create, for each language,
|
2016-10-20 00:34:31 +02:00
|
|
|
a resource file called `translations.json` for the frontend strings
|
|
|
|
and `django.po` for the backend strings.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
|
|
|
The `makemessages` command is idempotent in that:
|
2016-06-27 10:32:15 +02:00
|
|
|
|
2016-06-30 13:56:04 +02:00
|
|
|
- It will only delete singular keys in the resource file when they
|
|
|
|
are no longer used in Zulip code.
|
|
|
|
- It will only delete plural keys (see below for the documentation
|
|
|
|
on plural translations) when the corresponding singular key is
|
|
|
|
absent.
|
|
|
|
- It will not override the value of a singular key if that value
|
|
|
|
contains a translated text.
|
2016-05-06 13:22:37 +02:00
|
|
|
|
2016-10-20 00:34:31 +02:00
|
|
|
3. A Zulip maintainer uploads the resource files to Transifex using the
|
|
|
|
`tx push -s -a` command.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
2016-10-20 00:34:31 +02:00
|
|
|
4. Translators translate the strings in Transifex.
|
|
|
|
|
|
|
|
5. With some setup, anyone can download the updated resource files
|
|
|
|
from Transifex using the `tx pull -a` command. This command will
|
|
|
|
download the resource files from Transifex and replace your local
|
|
|
|
resource files with them.
|
|
|
|
|
|
|
|
6. One runs `python manage.py compilemessages` to compile the
|
|
|
|
translation strings so that they are will be used in the Zulip
|
|
|
|
development environment. This is run automatically during Zulip
|
|
|
|
development environment provisioning.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
|
|
|
## Backend Translations
|
|
|
|
|
|
|
|
All user-facing text in the Zulip UI should be generated by an HTML
|
2016-05-06 13:22:37 +02:00
|
|
|
template so that it can be translated.
|
|
|
|
|
|
|
|
Zulip uses two types of templates: backend templates (powered by the
|
|
|
|
[Jinja2][] template engine, though the original [Django][] template
|
|
|
|
engine is still supported) and frontend templates (powered by
|
2016-06-30 13:56:04 +02:00
|
|
|
[Handlebars][]).
|
2016-05-06 13:22:37 +02:00
|
|
|
|
|
|
|
To mark a string for translation in the Jinja2 and Django template
|
|
|
|
engines, you can use the `_()` function in the templates like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
{{ _("English text") }}
|
|
|
|
```
|
|
|
|
|
|
|
|
If a string contains both a literal string component and variables,
|
|
|
|
you can use a block translation, which makes use of placeholders to
|
2016-06-30 13:56:04 +02:00
|
|
|
help translators to translate an entire sentence. To translate a
|
2016-05-06 13:22:37 +02:00
|
|
|
block, Jinja2 uses the [trans][] tag while Django uses the
|
|
|
|
[blocktrans][] tag. So rather than writing something ugly and
|
|
|
|
confusing for translators like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
# Don't do this!
|
|
|
|
{{ _("This string will have") }} {{ value }} {{ _("inside") }}
|
|
|
|
```
|
|
|
|
|
|
|
|
You can instead use:
|
|
|
|
|
|
|
|
```
|
|
|
|
# Jinja2 style
|
|
|
|
{% trans %}This string will have {{ value }} inside.{% endtrans %}
|
|
|
|
# Django style
|
|
|
|
{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}
|
|
|
|
```
|
|
|
|
|
2016-06-30 13:56:04 +02:00
|
|
|
Zulip expects all the error messages to be translatable as well. To
|
2016-10-19 14:51:20 +02:00
|
|
|
ensure this, the error message passed to `json_error` and
|
|
|
|
`JsonableError` should always be a literal string enclosed by `_()`
|
|
|
|
function, e.g.:
|
2016-06-01 13:20:45 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
json_error(_('English Text'))
|
|
|
|
JsonableError(_('English Text'))
|
|
|
|
```
|
|
|
|
|
|
|
|
To ensure we always internationalize our JSON errors messages, the
|
|
|
|
Zulip linter (`tools/lint-all`) checks for correct usage.
|
|
|
|
|
2016-05-13 12:44:03 +02:00
|
|
|
## Frontend Translations
|
|
|
|
|
2016-06-30 13:56:04 +02:00
|
|
|
Zulip uses the [i18next][] library for frontend translations. There
|
|
|
|
are two types of files in Zulip frontend which can hold translatable
|
2016-10-19 14:51:20 +02:00
|
|
|
strings: JavaScript code files and Handlebar templates. To mark a
|
|
|
|
string translatable in JavaScript files, pass it to the `i18n.t`
|
|
|
|
function.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
i18n.t('English Text', context);
|
|
|
|
i18n.t('English text with a __variable__', {'variable': 'Variable value'});
|
|
|
|
```
|
|
|
|
|
2016-10-19 14:51:20 +02:00
|
|
|
Note: In the second example above, instead of enclosing the variable
|
|
|
|
with handlebars, `{{ }}`, we enclose it with `__` because we need to
|
|
|
|
differentiate the variable from the Handlebar tags. The symbol which
|
|
|
|
is used to enclose the variables can be changed in
|
|
|
|
`/static/js/src/main.js`.
|
2016-06-30 13:56:04 +02:00
|
|
|
|
|
|
|
`i18next` also supports plural translations. To support plurals make
|
|
|
|
sure your resource file contatins the related keys:
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
"en": {
|
|
|
|
"translation": {
|
|
|
|
"key": "item",
|
|
|
|
"key_plural": "items",
|
|
|
|
"keyWithCount": "__count__ item",
|
|
|
|
"keyWithCount_plural": "__count__ items"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
With this resource you can show plurals like this:
|
|
|
|
|
|
|
|
```
|
|
|
|
i18n.t('key', {count: 0}); // output: 'items'
|
|
|
|
i18n.t('key', {count: 1}); // output: 'item'
|
|
|
|
i18n.t('key', {count: 5}); // output: 'items'
|
|
|
|
i18n.t('key', {count: 100}); // output: 'items'
|
|
|
|
i18n.t('keyWithCount', {count: 0}); // output: '0 items'
|
|
|
|
i18n.t('keyWithCount', {count: 1}); // output: '1 item'
|
|
|
|
i18n.t('keyWithCount', {count: 5}); // output: '5 items'
|
|
|
|
i18n.t('keyWithCount', {count: 100}); // output: '100 items'
|
|
|
|
```
|
|
|
|
|
|
|
|
For further reading on plurals, read the [official] documentation.
|
|
|
|
|
|
|
|
To mark the strings as translatable in the Handlebar templates, Zulip
|
|
|
|
registers two Handlebar [helpers][]. The syntax for simple strings is:
|
|
|
|
|
|
|
|
```
|
|
|
|
{{t 'English Text' }}
|
|
|
|
```
|
|
|
|
|
|
|
|
The syntax for block strings or strings containing variables is:
|
|
|
|
|
|
|
|
```
|
|
|
|
{{tr context}}
|
|
|
|
Block of English text.
|
|
|
|
{{/tr}}
|
|
|
|
|
|
|
|
var context = {'variable': 'variable value'};
|
|
|
|
{{tr context}}
|
|
|
|
Block of English text with a __variable__.
|
|
|
|
{{/tr}}
|
|
|
|
```
|
2016-05-13 12:44:03 +02:00
|
|
|
|
2016-06-30 13:56:04 +02:00
|
|
|
The rules for plurals are same as for JavaScript files. You just have
|
2016-10-19 14:51:20 +02:00
|
|
|
to declare the appropriate keys in the resource file and then include
|
|
|
|
the `count` in the context.
|
2016-05-13 12:44:03 +02:00
|
|
|
|
|
|
|
|
2016-05-06 13:22:37 +02:00
|
|
|
[Django]: https://docs.djangoproject.com/en/1.9/topics/templates/#the-django-template-language
|
|
|
|
[Jinja2]: http://jinja.pocoo.org/
|
|
|
|
[Handlebars]: http://handlebarsjs.com/
|
|
|
|
[trans]: http://jinja.pocoo.org/docs/dev/templates/#i18n
|
|
|
|
[blocktrans]: https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#std:templatetag-blocktrans
|
2016-06-30 13:56:04 +02:00
|
|
|
[i18next]: http://i18next.com
|
|
|
|
[official]: http://i18next.com/translate/pluralSimple/
|
|
|
|
[helpers]: http://handlebarsjs.com/block_helpers.html
|
|
|
|
[resource]: http://i18next.com/translate/
|
2016-05-19 17:33:30 +02:00
|
|
|
|
|
|
|
## Testing Translations
|
|
|
|
|
|
|
|
First of all make sure that you have compiled the translation strings
|
|
|
|
using `python manage.py compilemessages`.
|
|
|
|
|
|
|
|
Django figures out the effective language by going through the
|
|
|
|
following steps:
|
|
|
|
|
|
|
|
1. It looks for the language code in the url.
|
2016-10-19 14:51:20 +02:00
|
|
|
2. It looks for the `LANGUAGE_SESSION_KEY` key in the current user's
|
2016-05-19 17:33:30 +02:00
|
|
|
session.
|
|
|
|
3. It looks for the cookie named 'django_language'. You can set a
|
2016-10-19 14:51:20 +02:00
|
|
|
different name through the `LANGUAGE_COOKIE_NAME` setting.
|
2016-05-19 17:33:30 +02:00
|
|
|
4. It looks for the `Accept-Language` HTTP header in the HTTP request.
|
|
|
|
Normally your browser will take care of this.
|
|
|
|
|
2016-10-19 14:51:20 +02:00
|
|
|
The easiest way to test translations is through the i18n URLs, e.g.,
|
|
|
|
if you have German translations available, you can access the German
|
|
|
|
version of a page by going to `/de/path_to_page` in your browser.
|
2016-05-19 17:33:30 +02:00
|
|
|
|
|
|
|
To test translations using other methods you will need an HTTP client
|
2016-10-19 14:51:20 +02:00
|
|
|
library like `requests`, `cURL` or `urllib`. Here is some sample code
|
|
|
|
to test `Accept-Language` header using Python and `requests`:
|
2016-05-19 17:33:30 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
import requests
|
|
|
|
headers = {"Accept-Language": "de"}
|
|
|
|
response = requests.get("http://localhost:9991/login/", headers=headers)
|
|
|
|
print(response.content)
|
|
|
|
```
|