translation_platform.mdwn 22.7 KB
Newer Older
1
2
3
[[!meta title="Translation platform"]]
[[!toc levels=2]]

Ulrike Uhlig's avatar
Ulrike Uhlig committed
4
Until 2019, our (website) translation infrastructure relied on
5
6
7
8
translators [[being able to know how to use
Git|contribute/how/translate/with_Git]]. This was a pretty high entry
barrier for new translators, especially those who are not familiar with
Git or the command line.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
9

Ulrike Uhlig's avatar
Reorder    
Ulrike Uhlig committed
10
11
12
13
This is the technical design documentation of our setup.

We also provide a dedicated [[documentation for translators on how to use
Weblate|contribute/how/translate/with_Weblate]] to contribute
14
translations.
Ulrike Uhlig's avatar
Reorder    
Ulrike Uhlig committed
15

16
17
18
19
20
21
22
Terms used in this document
===========================

- Canonical Git repository: the main Tails Git repository that our
  website relies on, in scripts often called "main repository" or "main
  Git"
- Production server: the server that hosts our website
23
- translate.lizard: the VM that hosts our Weblate webinterface, the
Ulrike Uhlig's avatar
Ulrike Uhlig committed
24
  corresponding Git repositories, as well as the staging website.
25

26
[Corresponding tickets on Redmine](https://redmine.tails.boum.org/code/projects/tails/issues?query_id=321)
27
28
29
30

Setup of our translation platform & integration with our infrastructure
=======================================================================

Ulrike Uhlig's avatar
Ulrike Uhlig committed
31
We are using our own [Weblate instance](https://translate.tails.boum.org/).
32

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
33
34
35
36
37
38
39
40
41
42
Weblate uses a clone of the Tails main Git repository, to which
translations get committed, once they have been approved by a user with
reviewer status. Non-approved translations live on Weblate's database
only, until they get reviewed. A staging website allows translators to
preview non-reviewed translations in context.

Approved changes are automatically fed back into our canonical Git
repository. This presents a major challenge, indeed we need to ensure
that:

Ulrike Uhlig's avatar
Ulrike Uhlig committed
43
44
45
46
47
48
49
50
51
- no merge conflicts occur:

  - such conflicts often occur on PO file headers which prevents Weblate
    from automatically merging changes
  - many contributors work on the same code base using different tools
    (PO files can be edited by hand, using translation software such as
    POedit, or they are generated by ikiwiki itself, which results in
    different formatting)

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
52
- only PO files are committed.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
53
- the committed PO files comply with shared formatting standards.
Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
54
55
56
57
- no compromised code is introduced.

In order to integrate Weblate and the work done by translators into our
process, we have set up this scheme:
58

59
<img src="git_repository_details.svg" />
60

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
61
62
Website and Weblate
-------------------
Ulrike Uhlig's avatar
Ulrike Uhlig committed
63

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
64
65
66
67
68
69
70
Our website uses ikiwiki and its PO plugin. It uses markdown files for
the English original language and carries a PO file for each translated
language. Thereby we distinguish languages that are activated on our
website from languages that have translations but are not yet activated
on the website because they do not [[cover enough of a portion of
our core pages|contribute/how/translate/team/new/]] to be considered
usable.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
71

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
72
73
74
75
76
77
78
We have defined [[a list of tier-1
languages|contribute/how/translate#tier-1-languages]], that we consider
to be of importance to our user base. No more languages shall be
activated in Weblate as our main Git repository carries reviewed, and
thus approved translations of all languages enabled on the Weblate
platform, while only part of them are active on the website.

79
Each PO file corresponds to a single component in Weblate, in order to
80
81
82
appear in the Weblate interface.

For example, the component:
83
84
85
86
87

    wiki/src/support.*.po

relates to the files support.mdwn, support.es.po, support.de.po, support.pot,
etc.
88

89
90
Repositories
------------
91

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
92
93
94
95
96
97
The repository used by Weblate is cloned and updated from the Tails main
repository, and its master branch. Changes generated on Weblate's copy
of the Tails main Git repository, located on the VM which hosts the
Weblate platform, are fed back to the Tails main repository, into the
master branch, automatically. This happens through a number of scripts,
checks, and cronjobs that we'll describe below.
98

Ulrike Uhlig's avatar
Ulrike Uhlig committed
99
There are several languages enabled, some of them with few or no
Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
100
101
translations. As everything is fed back to the Tails canonical
repository, all files are available when cloning this repository:
102

103
104
    git clone https://git-tails.immerda.ch/tails

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
105
106
If needed, for exceptional means, Weblate's Git repository can be cloned
or added as a remote:
107
108
109

    git clone https://translate.tails.boum.org/git/tails/index/

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
110
At the server the repository is located in
111
112
113

    ~weblate/repositories/vcs/tails/index

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
114
115
116
117
Weblate can commit to its local repository at any time, whenever
translations get approved. Changes done in the canonical repository by
Tails contributors via Git and changes done in Weblate thus need to be
merged - in a safe place. This happens in an integration repository:
118
119
120

    ~weblate/repositories/integration

Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
121
122
On the VM (translate.lizard), a third repository is used for the staging
website:
123
124
125

    ~weblate/repositories/vcs/staging

Ulrike Uhlig's avatar
Ulrike Uhlig committed
126
127
Automatic merging and pushing
-----------------------------
128

Ulrike Uhlig's avatar
Ulrike Uhlig committed
129
130
131
The integration work between the different repositories is done by a
script which is executed on the VM hosting Weblate as a cronjob every
XXX hours. The script
Ulrike Uhlig's avatar
Rewrite    
Ulrike Uhlig committed
132
[`cron.sh`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/cron.sh).
Ulrike Uhlig's avatar
Ulrike Uhlig committed
133
134
here has the following steps which we will explain hereafter:

135
136
137
138
139
140
141
142
(XXX: Why are steps 4 and 6 the same? And why does step 6 happen after
step 5?)

(XXX: please also briefly document the missing steps: 2,3,7: why are
they needed?)

  1. Canonical → Integration:
     Update the integration repository with changes made on the
Ulrike Uhlig's avatar
Ulrike Uhlig committed
143
144
     canonical repository (called "main" in the script)
  2. Lock translations on the web platform
Ulrike Uhlig's avatar
Ulrike Uhlig committed
145
     (XXX: It is unclear when this happens from the below steps)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
146
147
  3. Trigger Weblate to commit pending approved translations (`manage.py
     commit_pending`)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
148
     (XXX: It is unclear when this happens from the below steps)
149
150
151
152
153
154
  4. Weblate → Integration:
     Integrate comitted changes from Weblate into the integration repository
  5. Integration → Canonical:
     Push up-to-date integration repository to canonical repository
  6. Weblate→ Integration:
     Merging changes done on Weblate's repository into the integration
Ulrike Uhlig's avatar
Ulrike Uhlig committed
155
     repository
Ulrike Uhlig's avatar
Ulrike Uhlig committed
156
  7. Unlock translations for translators
Ulrike Uhlig's avatar
Ulrike Uhlig committed
157
     (XXX: It is unclear when this happens from the below steps)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
158
  8. Run `manage.py update_index`
Ulrike Uhlig's avatar
Ulrike Uhlig committed
159
     (XXX: It is unclear when this happens and what this means)
160
  9. Push to canonical Git and production server (XXX: is this correct?)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
161
162
163

Whenever a contributor modifies a markdown (`*.mdwn`) file, and pushes
to master, the corresponding POT and PO files are updated, that is: the
164
translateable English strings within those files are updated. This
Ulrike Uhlig's avatar
Ulrike Uhlig committed
165
166
167
168
169
170
171
172
173
174
175
update happens on the production server itself, when [[building the
wiki|contribute/build/website]] for languages that are enabled on the
production website.

We need to ensure on the translation platform server, that PO files for
additional languages (that are enabled on Weblate but not on the
production website) are equally updated, committed locally, pushed to
the canonical Git repository. On top of this we need to update Weblate's
database accordingly, so that translations can be added for new or
modified English strings in those files, in all languages.

176
177
### Step 1: Canonical → Integration

178
### Update the integration repository with changes made on the canonical repository
Ulrike Uhlig's avatar
Ulrike Uhlig committed
179
180
181

The script fetches from the canonical (remote) repository and tries to
merge changes into the (local) integration repository. The merge
Ulrike Uhlig's avatar
Ulrike Uhlig committed
182
strategy used for this step is defined in [`update_weblate_git.py`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/update_weblate_git.py): (XXX: Shouldn't this script be called update_integration_git.py according to this documentation?)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
183

Ulrike Uhlig's avatar
Ulrike Uhlig committed
184
185
186
187
188
189
190
When this script is called, it merges changes in PO files based on
single translation units (`msgids`). Merge conflicts occur when the same
translation unit has been changed in the canonical and the integration
repository (in the latter case, this would mean that the change has been
done via Weblate). In such a case, we always prefer the canonical
version. This makes sure that Tails developers can fix issues in
translations and have priority over Weblate.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
191
192
193
194

Due to this procedure we never end up with broken PO files, however, we
may loose a translation done on Weblate.

Ulrike Uhlig's avatar
Ulrike Uhlig committed
195
Until here, only PO files of languages that are activated on our
Ulrike Uhlig's avatar
Ulrike Uhlig committed
196
197
198
199
200
201
202
203
204
205
206
production website will be merged, as the production website, i.e. the
canonical Git repository does not regenerate those of non activated
languages.

Hence, after the activated language PO files are merged, the script
checks if PO files of additional, non-production activated languages
need updating. We do this by generating POT files out of a PO file that
we've previously defined as a default language.  If the actual POT file,
generated on the production server differs from the POT file we've just
created, then every additional language PO file needs to be updated.

207
208
209
On top of this, if the "defaultlang"s PO file (and its markdown file)
have been renamed, moved or deleted, than the PO files of additional
languages need to follow this step.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
210
211

Finally, the script will test if the new commit has triggered any change,
Ulrike Uhlig's avatar
Ulrike Uhlig committed
212
213
214
(XXX: I don't understand what "the new commit" is here, can you please
clarify?). If it has not, we'll simply perform a fast-forward (XXX:
please clarify on what this is performed).
215

216
217
### Step 4: Weblate → Integration

218
### Integrating the changes made in the canonical Git repository into Weblate repository and database
Ulrike Uhlig's avatar
Ulrike Uhlig committed
219
220
221
222
223
224
225
226
227
228

After having merged changes from the canonical Git repository into the
integration Git repository, and integrated changes from Weblate there,
we can assume that every PO file now is up-to-date (in the integration
repository). Hence we can try to pull (XXX: from the integration to
Weblate's repository?) using a fast-forward. (XXX: what does it mean "we
can try?", who or what tries it when?)

If a fast-forward is not possible then the Canonical <-> Integration
loop has something to do (XXX: What does this mean "something to do"?
229
and how do we know it's not possible?) and we try again later to pull.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
230
231
232
233
234
235
236
237
238
239
240
241
(XXX: What is this triggered by? What does "later" mean in this
context?)

If the fast-forward was successful, we need to update Weblate's components
to reflect the modifications that happened on the side of Git, such as
string and file updates, removals, renames, or additions. This is
handled by another script:
[`update_weblate_components.py`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/update_weblate_components.py).

There may be other scripts (XXX: like what?), that may update the master
branch of Weblate repo besides our script, that's why the script is
using an own Git remote named "cron" (XXX: is this information
242
up-to-date? I cannot find it in the script. If this is a config
Ulrike Uhlig's avatar
Ulrike Uhlig committed
243
244
245
variable, let's make it clear. I also don't understand what this own Git
remote is, where does it come from, is it up-to-date?) to keep track of which commits need to
be scanned (XXX: scanned, really?) for Weblate component changes.
246

247
248
### Step 6: Weblate → Integration

249
### Merging changes from Weblate's Git repository into the integration repository
250

Ulrike Uhlig's avatar
Ulrike Uhlig committed
251
252
253
254
Weblate's Git repository is not bare. Hence we need to pull changes
committed to Weblate's Git repository and merge them into the
integration repository. This is done by the script
[`merge_weblate_changes.py`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/merge_weblate_changes.py).
255

Ulrike Uhlig's avatar
Ulrike Uhlig committed
256
257
258
259
260
261
Changes already present in the integration repository are preferred over
the changes from the remote, Weblate repository. This is to allow fixes
done to PO files manually, via the canonical Git repository.

Again, PO file merges are done on translation units (`msgids`).

262
Furtheremore, we make sure via the script that Weblate has only modified
Ulrike Uhlig's avatar
Ulrike Uhlig committed
263
264
265
PO files; indeed we automatically reset everything else to the version
that exists in canonical.

266
267
### Step 9: Integration → Canonical

268
### Pushing from the integration repository to our canonical repository, aka "production"
269

270
As a last step, a push from the VM hosting Weblate (XXX: to where?) is done, using
Ulrike Uhlig's avatar
Ulrike Uhlig committed
271
the integration repository that has all changes:
272

Ulrike Uhlig's avatar
Ulrike Uhlig committed
273
274
275
276
277
278
279
280
On the side of the canonical Git repository, gitolite has a special
hook,
[`tails-weblate-update.hook`](https://git-tails.immerda.ch/puppet-tails/tree/files/gitolite/hooks/tails-weblate-update.hook),
to make sure that Weblate is only allowed to push changes on PO files.
This hook also checks and verifies the committer of each commit, to make
sure only translations made on the Weblate platform are automatically
pushed, and no other changes than those on PO files accepted. Otherwise
the push is rejected, for security reasons.
281

282
283
<a id="staging-website"></a>

284
285
286
Staging website
---------------

Ulrike Uhlig's avatar
Ulrike Uhlig committed
287
288
289
290
291
In order to allow translators to see their non committed suggestions as
well as languages which are not activated on https://tails.boum.org we
have put in place a [staging website](https://staging.tails.boum.org/) .
It is a clone of our production website and is regularly refreshed.

292
On top of what our production website has, it includes:
293

294
295
296
297
 - all languages available on Weblate, even those that are not enabled
   on our production website yet;

 - all translation suggestions made on Weblate.
298

299
This allows:
300

301
302
 - translators to check how the result of their work will look like
   on our website;
303

304
305
 - reviewers to check how translation suggestions look like on the
   website, before validating them.
306

307
 - check the sanity-check-website report:
308

Ulrike Uhlig's avatar
Ulrike Uhlig committed
309
   [https://staging.tails.boum.org/last-sanity-errors.txt](https://staging.tails.boum.org/last-sanity-errors.txt)
310

Ulrike Uhlig's avatar
Ulrike Uhlig committed
311
### What is done behind the scene to generate a new version of the staging website?
312

313
The cronjob
Ulrike Uhlig's avatar
Ulrike Uhlig committed
314
[`update-staging-website.sh`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/update-staging-website.sh)
315
is run.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
316

317
318
319
320
This cronjob calls a script that extracts suggestions from Weblate's
database and applies them to a local clone of Weblate's Git repository,
after having updated the clone with newer data from Weblate's VCS.
[`save-suggestions.py`](https://git-tails.immerda.ch/puppet-tails/tree/files/weblate/scripts/save-suggestions.py)
Ulrike Uhlig's avatar
Ulrike Uhlig committed
321

322
After that we run ikiwiki --refresh using an dedicated `ikiwiki.setup`
323
file for the staging website.
324

325
326
327
None of the changes on this repository clone are fed back anywhere and they
should not.

328
329
<a id="access-control"></a>

330
Access control on the Weblate platform
Ulrike Uhlig's avatar
Ulrike Uhlig committed
331
======================================
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352

### Requirements

- Every translation change must be reviewed by another person before
  it's validated (and thus committed by Weblate and pushed to our
  production website).

  - This requirement must be enforced via technical means, for
    translators that are not particularly trusted (e.g. new user
    accounts). For example, it must be impossible for an attacker to
    pretend to be that second person and validate their own changes,
    simply by creating a second user account.

  - It's acceptable that this requirement is enforced only via social
    rules, and not via technical means, for a set of
    trusted translators.

- We need to be able to bootstrap a new language and give its
  translators sufficient access rights so that they can do their job,
  even without anyone at Tails personally knowing any of them.

intrigeri's avatar
intrigeri committed
353
354
355
- Suggested translations are used to build the [[staging
  website|translation_platform#staging-website]].

Ulrike Uhlig's avatar
Ulrike Uhlig committed
356
357
Currently implemented proposal
------------------------------
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377

- In Weblate lingo, we use the [dedicated
  reviewers](https://docs.weblate.org/en/latest/workflows.html#dedicated-reviewers)
  workflow: it's the only one that protects us against an adversary
  who's ready to create multiple user accounts.

- When not logged in, a visitor is in the `Guests` group and is
  only allowed to suggest translations.

- Every logged in user is in the `Users` group. Members of this group
  are allowed to suggest translations but not to accept suggestions
  nor to directly save new translations of their own.

- A reviewer, i.e. a member of the `@Review` group in Weblate, is
  allowed to accept suggestions.

  Limitations:

  - Technically, reviewers are also allowed to directly save new
    translations of their own, edit existing translations, and
378
    accept their own suggestions; we ask them in our
379
380
381
382
383
384
385
386
387
388
    documentation to use this privilege sparingly, only to fix
    important and obvious problems.

	Even if we forbid reviewers to accept their own suggestions,
    nothing would prevent them from creating another account, making
    the suggestion from there, and then accepting it with their
    reviewer account.

  - Reviewer status is global to our Weblate instance, and not
    per-language, so technically, a reviewer can very well accept
389
    suggestions for a language they don't speak. We will them in
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    our documentation to _not_ do that, except to fix important and
    obvious problems that don't require knowledge of that language
    (for example, broken syntax for ikiwiki directives).

	If this ever causes actual problems, this could be fixed with
    [group-based access
    control](https://docs.weblate.org/en/weblate-2.20/admin/access.html#groupacl)

- How one gets reviewer status:

  - We will port to Weblate semantics the pre-existing trust
    relationship we already have towards translation teams that have
    been using Git so far: they all become reviewers.

404
	To this aim, we have asked them to create an account on Weblate
405
406
407
	and tell us what their user name is.

  - One can request reviewer status to Weblate administrators, who
intrigeri's avatar
intrigeri committed
408
409
410
411
412
413
414
415
    will:
    1. Accept this request if, and only if, a sufficient amount of
       work was done by the requesting translator (this can be checked on
       the user's page, e.g.
       [intrigeri's](https://translate.tails.boum.org/user/intrigeri/).
       In other words, we use proof-of-work to increase the cost of attacks.
    2. Let <tails-l10n@boum.org> and all the other Weblate reviewers
       know about this status change.
416
417
418
419
420

- Bootstrapping a new language

  As a result of this access control setup, translators for a new
  language can only make suggestions until they have done a sufficient
421
422
423
  amount of work and two of them are granted reviewer status. In the
  meantime, they can see the output of their work on the [[staging
  website|blueprint/translation_platform#staging-website]].
424
425

  Pending questions:
426

427
428
429
  - Is the resulting UX good enough? Would it help if we allowed them
    to vote up suggestions, even if this does not result in the
    suggestion to be accepted as a validated translation?
intrigeri's avatar
intrigeri committed
430
    (At the moment, suggestion voting is disabled.)
431

Ulrike Uhlig's avatar
Ulrike Uhlig committed
432
433
434
435
436
Weblate installation and maintenance
====================================

A hybrid approach
-----------------
437

Ulrike Uhlig's avatar
Ulrike Uhlig committed
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
The Tails infrastructure uses Puppet to make it easier to enforce and
replicate system configuration, and usually relies on Debian packages to
ensure stability of the system. But the effort to maintain a stable
system somehow conflicts with installing and maintaining Weblate, a
Python web application, which requires using up-to-date versions of
Weblate itself and of its dependencies.

Having that in mind, and taking into account that we already started
using Docker to replicate the translation server environment to
experiment with upgrading and running an up-to-date version of Weblate,
it can be a good trade-off to use Puppet to provide an environment to
run Docker, and to use a Docker container to actually run an up-to-date
Weblate installation.

From the present state of the Docker image, which currently uses
(slightly modified/updated) Puppet code to configure the environment and
then sets up Weblate, the following steps could be taken to achieve a
new service configuration as described above:
456
457

* Move the database to a separate Docker service.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
458
459
460
461
462
* Remove all Puppet code from the Docker image: inherit from the
  simplest possible Docker image and setup a Weblate Docker image with
  all needed dependencies.
* Modify the Puppet code to account for setting up an environment that
  has Docker installed and that runs the Weblate Docker image.
463
464
* Set up persistence for the Weblate git repository and configuration.
* Set up persistence and backups for the database service.
Ulrike Uhlig's avatar
Ulrike Uhlig committed
465
466
467
468
469
470
471
472
* Update the Puppet code to run tmserver (if/when it's needed -- latest
  Weblate accounts for basic suggestions using its own database).

After that, we should have a clear separation between stable
infrastructure maintenance using Debian+Puppet in one side and
up-to-date Weblate application deployment using Docker in the other
side. The Docker image would have to be constantly maintained to account
for Weblate upgrades, but that should be easier cleaner than deploying
473
474
475
476
477
478
479
480
Weblate directly on the server.

Long-term maintenance plan
--------------------------

This is work in progress. A plan for the future maintenance of our
Weblate instance will be worked on in November 2019 and laid out here
before the end of the year.
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533

Choosing a translation web platform
===================================

These are the requirements that we have defined for our translation web platform.

MUST
----

* provide a usable easy web interface
* be usable from Tor Browser
* automatic pull from main Git repo
* provide a common glossary for each language, easy to use and improve
* allow translators to view, in the correct order, all strings that
  come from the entire page being translated, both in English and in
  the target language
* make it easy to view the translations in context i.e. when translating
  an entire page, all strings to be translated should only come from
  this page. translators should be able to view the page in context.
* provide user roles (admin, reviewer, translator)

SHOULD
------

* be "privacy sensitive", i.e. be operated by a non-profit
* allow translators to push translations through Git (so that core
  developers only have to fetch reviewed translations from there)
* provide support for Git standard development branches (devel, stable,
  and testing) but we could also agree upon translation only master
  through this interface
* provide checks for inconsistent translations
* provide feature to write/read comments between translators

MAY
---

* allow translating topic branches without confusing translators,
  causing duplicate/premature work, fragmentation or merge conflicts
  -- e.g. present only new or updated strings in topic branches;
  see <https://mailman.boum.org/pipermail/tails-l10n/2015-March/002102.html>
  for details
* provide a feature to easily see what is new, what needs updating, what are translation priorities
* provide possibility to set up new languages easily
* send email notifications
  - to reviewers whenever new strings have been translated or updated
  - to translators whenever a resource is updated
* respect authorship (different committers?)
* provide statistics about the percentage of translated and fuzzy strings
* Letting translators report about problems in original strings, e.g.
  with a "Report a problem in the original English text" link, that
  e.g. results in an email being sent to -l10n@ or -support-private@.
  If we don't have that, then [[contribute/how/translate]] MUST
  document how to report issues in the original English text.