zulip/docs/translating.md

250 lines
8.8 KiB
Markdown
Raw Normal View History

# Translating Zulip
Zulip has full support for Unicode, so you can already use your
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!
If you're interested in contributing translations to Zulip, please
[join the "translation" stream in our developers' Zulip
chat](https://zulip.tabbott.net/#narrow/stream/translation), and say
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.
## Setting Default Language in Zulip
Zulip allows you to set the default language through the settings
page, in the 'Display Settings' section. The URL will be
`/#settings/display-settings` on your realm.
## 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
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`.
## 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:
1. Mark the strings for translations (see sections for
[backend](#backend-translations) and
[frontend](#frontend-translations) translations for details on
this).
2. Create JSON formatted [resource][] files using the `python manage
makemessages` command. This command will create a resource file
called `translations.json` for frontend and `django.po` for backend
for every language under `static/locale`. The location for frontend
resource file can be changed by passing an argument to the command
(see the help for the command for further details). However, make
sure that the location is publicly accessible since frontend files
are loaded through XHR in the frontend which will only work with
publicly accessible resources.
The `makemessages` command is idempotent in that:
- 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.
3. Upload the resource files to Transifex using the `tx push -s -a`
command.
4. 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.
## Backend Translations
All user-facing text in the Zulip UI should be generated by an HTML
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
[Handlebars][]).
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
help translators to translate an entire sentence. To translate a
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 %}
```
Zulip expects all the error messages to be translatable as well. To
ensure this, the error message passed to `json_error` and
`JsonableError` should always be a literal string enclosed by `_()`
function, e.g.:
```
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.
## Frontend Translations
Zulip uses the [i18next][] library for frontend translations. There
are two types of files in Zulip frontend which can hold translatable
strings: JavaScript code files and Handlebar templates. To mark a
string translatable in JavaScript files, pass it to the `i18n.t`
function.
```
i18n.t('English Text', context);
i18n.t('English text with a __variable__', {'variable': 'Variable value'});
```
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`.
`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}}
```
The rules for plurals are same as for JavaScript files. You just have
to declare the appropriate keys in the resource file and then include
the `count` in the context.
[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
[i18next]: http://i18next.com
[official]: http://i18next.com/translate/pluralSimple/
[helpers]: http://handlebarsjs.com/block_helpers.html
[resource]: http://i18next.com/translate/
## 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.
2. It looks for the `LANGUAGE_SESSION_KEY` key in the current user's
session.
3. It looks for the cookie named 'django_language'. You can set a
different name through the `LANGUAGE_COOKIE_NAME` setting.
4. It looks for the `Accept-Language` HTTP header in the HTTP request.
Normally your browser will take care of this.
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.
To test translations using other methods you will need an HTTP client
library like `requests`, `cURL` or `urllib`. Here is some sample code
to test `Accept-Language` header using Python and `requests`:
```
import requests
headers = {"Accept-Language": "de"}
response = requests.get("http://localhost:9991/login/", headers=headers)
print(response.content)
```