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, Hindi, 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 #translation
in the Zulip development community server, and
say hello. And please join the
Zulip project on Transifex
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.

Zulip has full support for Unicode, so you can already use your
preferred language everywhere in Zulip.

A great first step when getting started translating Zulip into a new
language is to write a style guide, since it greatly increases the
ability of future translators to translate in a way that’s consistent
with what your work.

We expect that all the English translatable strings in Zulip are
properly capitalized in a way consistent with how Zulip does
capitalization in general. This means that:

The first letter of a sentence or phrase should be capitalized.

Correct: “Manage streams”

Incorrect: “Manage Streams”

All proper nouns should be capitalized.

Correct: “This is Zulip”

Incorrect: “This is zulip”

All common words like URL, HTTP, etc. should be written in their
standard forms.

Correct: “URL”

Incorrect: “Url”

We have a tool to check for the correct capitalization of the
translatable strings; this tool will not allow the Travis builds to
pass in case of errors. You can use our capitalization checker to
validate your code by running ./tools/check-capitalization. If you
think that you have a case where our capitalization checker tool
wrongly categorizes a string as not capitalized, you can add an
exception in the tools.lib.capitalization.IGNORED_PHRASES list to
make the tool pass.

Please, stick to these while translating, and feel free to point out
any strings that should be improved or fixed.

Please note that you don’t need to do this if you’re translating; this is
only to describe how the whole process is. If you’re interested in
translating, you should check out the
translators’ workflow.

The strings are marked for translation (see sections for
backend and
frontend translations for details on
this).

Translation resource files are created using the ./manage.pymakemessages 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.

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.

Those resource files are uploaded to Transifex by a maintainer using the
./tools/i18n/push-translations command.

Translators translate the strings in Transifex.

The translations are downloaded back into the codebase by a
maintainer, using tools/i18n/sync-translations (which invokes txpull, internally).

These are the steps you should follow if you want to help to translate
Zulip:

Join us on Zulip and ask for access to the organization, as
described at the beginning.

Make sure you have access to Zulip’s dashboard in Transifex.

Ask a maintainer to update the strings.

Translate the strings for your language in Transifex.

Some useful tips for your translating journey:

Follow your language’s translation guide.
Keeping it open in a tab while translating is very handy. If one
doesn’t exist one, write one as you go; they’re easiest to write as
you go along and will help any future translators a lot.

First of all, download the updated resource files from Transifex using the
txpull-a--mode=developer command (it will require some
initial setup). This command will download the
resource files from Transifex and replace your local resource files with
them.

Then, make sure that you have compiled the translation strings using
./manage.pycompilemessages.

Django figures out the effective language by going through the following
steps:

It looks for the language code in the url (e.g. /de/).

It looks for the LANGUAGE_SESSION_KEY key in the current user’s
session.

It looks for the cookie named ‘django_language’. You can set a
different name through the LANGUAGE_COOKIE_NAME setting.

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:

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.

Zulip makes use of the Jinja2 templating system for the backend
and Handlebars for the frontend. Our HTML templates
documentation includes useful information on the syntax and
behavior of these systems.

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:

{{_("English text")}}

If a piece of text 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. So rather than writing
something ugly and confusing for translators like this:

A string in Python can be marked for translation using the _() function,
which can be imported as follows:

fromdjango.utils.translationimportugettextas_

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.:

Just like in JavaScript code, variables are enclosed in double
underscores __.

Handlebars expressions like {{variable}} or blocks like
{{#if}}...{{/if}} aren’t permitted inside a {{#tr}}...{{/tr}}
translated block, because they don’t work properly with translation.
The Handlebars expression would be evaluated before the string is
processed by i18n.t, so that the string to be translated wouldn’t be
constant. We have a linter to enforce that translated blocks don’t
contain handlebars.

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.