zumra-mobile / docs/howto/translations.md
Managing translations
Zumra Mobile Apps for Android & iOS
Managing translations
A person using the Zulip app can choose from a large number of languages for the app to present its UI in.
Within the running app, we use a library react-intl to get the
appropriate translation for a given string ("message") used in the UI.
To manage the set of UI messages and translations for them, and provide a nice workflow for people to contribute translations, we use (along with the rest of the Zulip project) a service called Transifex.
Translators
If you use a language other than English, we would be glad to have your help in translating Zulip! See Zulip's docs on translating for how to join us on Transifex and contribute.
All code contributors: wiring our code for translations
All text that appears in our UI should be translated. This takes a few different forms in different contexts in our code:
-
When the text is passed to one of our own components as a prop, look at the type of the prop. If it's
LocalizableText(which is a supertype ofstring), then the component will take responsibility for translating it before display. Most of our components do this, notablyLabelandZulipButton.- Conversely, if adding a new component which accepts as a prop some
text to show in the UI, generally the component should take the
prop as type
LocalizableTextand take responsibility for translating it -- either by passing to another such component, or with one of the other forms below.
- Conversely, if adding a new component which accepts as a prop some
text to show in the UI, generally the component should take the
prop as type
-
Otherwise, use the
_function:_('Hello, world')will return'Hello, world'if the user's chosen language is English,'Hola, mundo'if the user's chosen language is Spanish (well, it would if we had that string in our translation database -- see below), and so on. This function has typeGetText, and can be acquired from the React context; see jsdoc onGetTextfor details. -
If the message is not constant but requires interpolating some data, then:
- If using
_, use the following form:_('Hello, {name}', { name }). Translators will translate the constant string'Hello, {name}', including the placeholder. - If passing to something that accepts
LocalizableText, then use the same placeholder syntax in the message string, and see theLocalizableTexttype definition for where to put the message string and the data values. - For the full syntax available in message strings, see upstream docs from React Intl, Format.JS, and ICU.
- If using
-
In all of the above cases, if you're adding a new message string or editing an existing one, we'll want to get it into our translation database so that translators can translate it. To do this:
-
In the same commit that adds or edits the message, update the file
static/translations/messages_en.jsonaccordingly. This is the version-controlled source record of what message strings we need translations for. -
When your changes are merged, a maintainer will run the steps below to sync those changes up to Transifex. This makes it available for translators to translate there.
-
From time to time, and in particular just before preparing a release, a maintainer will run the steps below to sync translations from Transifex back down into the other files
static/translations/messages_*.jsonin our tree. This is the copy of our translation database that gets built into the app and used for finding translations.
-
Important general background on providing strings for translation:
-
Never try to concatenate translated strings together, or do other string manipulation on them. Instead, all punctuation, sentence structure, etc., should appear inside the constant message string. To interpolate data that can vary, use the placeholder syntax
'Hello, {name}'.This is important because different languages will put things in different orders in a sentence, and use different punctuation and spacing.
For further discussion, see general Zulip docs on internationalization.
Maintainers: syncing to/from Transifex
This section describes steps done by the maintainers of the Zulip mobile app, i.e. people who merge PRs into it. For everyone else, see the sections above.
Setup
You'll want Transifex's CLI client, tx.
-
Install in your homedir with
pip3 install --user transifex-client. Or you can use your Zulip dev server virtualenv, which has it. -
Configure a
.transifexrcwith your API token. See upstream instructions.This can go either in your homedir, or in your working tree to make the configuration apply only locally; it's already ignored in our
.gitignore. -
You'll need to be added as a "maintainer" to the Zulip project on Transifex. (Upstream recommends this as the set of permissions on a Transifex project needed for interacting with it as a developer.)
Regular operation
To sync with Transifex, run tools/tx-sync.
This syncs in both directions, and makes local commits with any changes. Review the results and then push to the central repo.
The sync uploads from static/translations/messages_en.json to the
set of strings Transifex shows for contributors to translate, and
downloads translations to files static/translations/messages_*.json.
(See .tx/config for how those paths are configured.)
For more details, see the usage message: tools/tx-sync --help.
Then look at the following sections to see if further updates are needed to take full advantage of the new or updated translations.
Updating the languages supported in the code
Sometimes when downloading translated strings we get a file for a new language. This happens when we've opened up a new language for people to contribute translations into in the Zulip project on Transifex, which we do when someone expresses interest in contributing them.
Each messages file in static/translations/ should be reflected in
two boring, more-or-less mechanical lists:
flow-typed/translations.jssrc/i18n/messages.js
The first of these has a comment with a trivial command to help automate updating it. The other is smaller, and is maintained manually. It'd be good to fully automate both of these; we haven't yet.
So, when a new messages file appears, update those three lists. Then see if the next section applies too...
Updating the languages offered in the UI
A fourth list should reflect many but not all of the messages files in
static/translations/: only the ones with a significant number of
strings actually translated. This is src/settings/languages.js,
which controls the languages offered in the language-picker screen in
our settings UI.
This would also be nice to automate, though a bit more complex, and to date we haven't.
So, when a language has gone from mostly untranslated, or completely
absent, to significantly translated, update the list in
src/settings/languages.js to include it. See there for details on
what counts as "significantly translated", and for an easy way to
check.