Weblate server
This page describes how the Weblate service running at translate.rx.studio was configured, and how it is maintained.
Infrastructure
The service runs on a single t3a.large
node with 20GB storage (was extended from 10GB due to Docker pulls) in the us-east-1
region of AWS, with termination protection enabled. AWS Backup is configured to take weekly snapshots of the node with a retention window of 1 year.
Docker images and configuration
Weblate is running using Docker and Docker compose:
version: '3'
services:
weblate:
image: weblate/weblate
volumes:
- weblate-data:/app/data
env_file:
- ./environment
restart: always
depends_on:
- database
- cache
environment:
WEBLATE_ENABLE_HTTPS: 1
WEBLATE_IP_PROXY_HEADER: HTTP_X_FORWARDED_FOR
database:
image: postgres:14-alpine
env_file:
- ./environment
volumes:
- postgres-data:/var/lib/postgresql/data
restart: always
cache:
image: redis:6-alpine
restart: always
command: [redis-server, --save, '60', '1']
volumes:
- redis-data:/data
https-portal:
image: steveltn/https-portal:1
ports:
- 80:80
- 443:443
restart: always
environment:
STAGE: production
PROXY_READ_TIMEOUT: 3600
CLIENT_MAX_BODY_SIZE: 100M
volumes:
- ssl-certs:/var/lib/https-portal
volumes:
weblate-data: {}
postgres-data: {}
redis-data: {}
ssl-certs: {}
The environment
file has no modifications, as the env var overrides are defined in the docker-compose-https.override.yml
file:
version: '3'
services:
weblate:
environment:
WEBLATE_EMAIL_HOST: email-smtp.us-east-1.amazonaws.com
WEBLATE_EMAIL_PORT: 587
WEBLATE_EMAIL_USE_SSL: 0
WEBLATE_EMAIL_USE_TLS: 1
WEBLATE_EMAIL_HOST_USER: *******
WEBLATE_EMAIL_HOST_PASSWORD: *******
WEBLATE_SERVER_EMAIL: noreply@translate.rx.studio
WEBLATE_DEFAULT_FROM_EMAIL: noreply@translate.rx.studio
WEBLATE_SITE_DOMAIN: translate.rx.studio
WEBLATE_ADMIN_EMAIL: *******
WEBLATE_ADMIN_PASSWORD: *******
# TODO migrate to Google Analytics v4
WEBLATE_GOOGLE_ANALYTICS_ID: UA-*******-*
# enable daily pulls from R core subversion
WEBLATE_AUTO_UPDATE: true
https-portal:
environment:
DOMAINS: 'translate.rx.studio -> http://weblate:8080'
The current image ids:
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
weblate/weblate latest 2ea7d086a8c4 4 days ago 1.1GB
redis 6-alpine 3616f0c0705d 7 days ago 27.1MB
postgres 14-alpine 9d94e6318ef2 7 days ago 242MB
steveltn/https-portal 1 1f41166c2e81 2 weeks ago 351MB
weblate/weblate <none> 02c6327da166 9 months ago 804MB
postgres <none> 07c710d28b91 9 months ago 216MB
redis <none> 57c580553a4d 10 months ago 25.5MB
postgres <none> ea498678e2bd 10 months ago 189MB
weblate/weblate <none> 475045de4358 10 months ago 847MB
steveltn/https-portal <none> 26e5bea459df 12 months ago 274MB
Current weblate etc versions listed at https://translate.rx.studio/about/
Commands to start the service:
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml build
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml up
To update, follow the official docs, but in short:
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml pull
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml down
docker-compose -f docker-compose-https.yml -f docker-compose-https.override.yml up -d
Quarterly patch for R Core
NB: Weblate links below assume you’re logged in there, otherwise you’ll be redirected to the home page
To generate a report on the translation updates in a time period:
- Visit https://translate.rx.studio/projects/r-project/#reports
- Select time period and generate report in rST format
- Convert ~markdown to HTML and share in the R Contributors slack group’s #core-translation channel
To submit a patch file on the translations found in Weblate but not in the trunk of the main R subversion repo:
The basic idea is to compare the Weblate repo (which copies the R subversion repo, but also adds translations provided via Weblate) to the “official” R sources; any difference in .po files should be submitted as a patch.
Make sure the Weblate repo is fully up-to-date. Check status at https://translate.rx.studio/projects/r-project/#repository – be sure there are no
Update
orCommit
actions needed.Get the two repos cloned on any machine. If this is your first time generating a patch on this machine:
mkdir weblate-diff && cd weblate-diff git clone -o weblate https://translate.rx.studio/git/r-project/base-r-gui/ . # remote #1: Weblate source git remote add svn git@github.com:r-devel/r-svn.git # remote #2: SVN source git fetch svn main # retrieve the latest from SVN
If you’ve got the repo set up locally already, update:
git fetch svn main git fetch weblate master git reset --hard weblate/master # force-reset to match state on Weblate (usual caution with --hard applies)
Make needed manual changes to reduce noise and ensure quality, e.g. something like:
library(data.table) library(crayon) library(logger) <- \() list.files(pattern = '\\.po$', recursive = TRUE) po_files # pocount is available from e.g. 'apt install translate-toolkit' <- function(f) { po_counts = fread(cmd = paste('pocount --csv', paste(f, collapse = " ")), sep = ',', fill = TRUE) x setnames(x, c("Filename", "Translated Messages"), c("filename", "n_translated")) x }<- \(branch) system2('git', c('checkout', branch)) set_branch set_branch('weblate/master') <- tools::checkPoFiles("") po_report if (length(po_report)) { print(po_report) stop("Fix above issues in .po files identified by tools::checkPoFiles() to proceed.") } <- po_counts(po_files()) weblate_summary set_branch('svn/main') <- po_counts(po_files()) svn_summary set_branch('master') # SUMMARIZE the changed translations, and delete empty .po files <- merge(weblate_summary, svn_summary, by = "filename", all = TRUE, suffixes = c("_weblate", "_svn")) po_summary := basename(dirname(dirname(filename)))] po_summary[, package # Drop empty & record files == 0 & is.na(n_translated_svn), { # NB: keep empty files if they're already in SVN, hence is.na() check po_summary[n_translated_weblate log_info('Dropping {.N} empty files:') = package, { .SD[, by log_level(INFO, 'From package {blue(.BY$package)}:') log_level(INFO, ' {toString(green(basename(filename)))}', .topenv = .SD) # NB: Need .SD since data.table doesn't pick up the variable from the string }]unlink(filename) NULL }]<- po_summary[n_translated_weblate > 0] po_summary # (optional) Summarize the update for the rest of the files setnafill(po_summary, fill = 0L, cols = which(sapply(po_summary, is.numeric))) := n_translated_weblate - n_translated_svn] po_summary[, n_newly_translated > 0, { po_summary[n_newly_translated log_info('New translations in {.N} files') = package, { .SD[, by log_level(INFO, 'From package {blue(.BY$package)}:') log_level(INFO, ' {sprintf("%s [+%s]", green(format(basename(filename))), red(format(n_newly_translated)))}', .topenv = .SD) }]NULL }] # REPLACE 'Report-Msgid-Bugs-To' metadata field pointing to #core-translation-po-bugs system2("sed", c("-i", "'s/sourcestringbugs.o40l9j@zapiermail.com/bugs.r-project.org/'", po_summary$filename)) # CHECK msgfmt works -- 'make update*' commands run by R-core check this as well <- tempfile() tmp ## NB: lapply() over for() to show all failures at once <- suppressWarnings(lapply( res $filename, po_summarysystem2("msgfmt", c("-c", "--statistics", "-o", tmp, po_file), stdout=TRUE, stderr=TRUE) \(po_file) ))<- which(!sapply(res, \(r) is.null(attr(r, "status")))) failed if (length(failed)) { cat("'msgfmt' failed for some .po files:\n") for (idx in failed) { cat(sprintf(" %s:\n", red(po_summary$filename[idx]))) writeLines(paste0(" ", res[[idx]])) } }
Generate a patch file from the diff, going back to the most recent commit with translations merged, e.g.
# NB: _not_ 'git diff weblate/master svn/main' since we've just deleted the empty .po files locally git diff svn/main --no-prefix -- "*.po"
Share the patch file on the R Contributors Slack group’s #core-translation channel and kindly ping @MichaelLawrence for his assistance on getting the patch file applied on the trunk of R dev to get it merged. We should do this ~once per quarter.
Maintenance
Components might become locked due to “not being able to push back to the upstream repo” (expected) or when upstream is not updated for a long time. In such case, components can be unlocked on the UI, but this needs to happen at the component level, so better to use the CLI instead:
weblate unlock_translation r-project
When there’s a conflict between SVN and the Weblate git repo, and Weblate cannot pull from the upstream repo, you will need to resolve the conflicts manually after seeing an error like the below:
Rebasing (1/210)
Rebasing (2/210)
...
Rebasing (50/210)
error: could not apply 93fd9017... Added translation using Weblate (Spanish)
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 93fd9017... Added translation using Weblate (Spanish)
CONFLICT (add/add): Merge conflict in src/library/stats/po/es.po
Auto-merging src/library/stats/po/es.po
rebase refs/remotes/origin/trunk: command returned error: 1
Weblate will provide a suggestion on how to fix, but in short:
SSH to the Weblate server
Make a backup on the Git repo before messing with the rebase process:
tar -zcvf /root/base-r-gui-repo-$(date '+%Y-%m-%d').tar.gz \ /var/lib/docker/volumes/weblate-docker_weblate-data/_data/vcs/r-project/base-r-gui
Attach the Docker container (this image is offered by
weblate
):sudo docker exec -ti weblate-docker_weblate_1 bash
Enter the git folder:
cd app/data/vcs/r-project/base-r-gui
As noted above when we made a copy of the repo, this folder can also be found in Weblate outside Docker:
/var/lib/docker/volumes/weblate-docker_weblate-data/_data/vcs/r-project/base-r-gui
Pull from SVN and start the rebase process to see the actual errors:
git svn fetch git svn rebase
(repeat until rebase completes) Resolve any conflicts that come up, usually using the script
deconflict_add_continue.py
found at the repo root:./deconflict_add_continue.py weblate
This finds the one file causing conflicts, selects the changes from Weblate (in order to preserve string edit history in the UI), removes the changes from SVN and the git conflict markers, then continues the rebase process. It also prints some helpful output, e.g. the number of conflicts resolved and the
git status
which tells about progress in the ongoing rebase.For now, if there are more than one files affected by a given rebase step (e.g. more than one file marked
both modified:
in thegit status
output), these needed to be edited manually (with the text editor of your choice, e.g.nano
ormcedit
), then added (git add
) before continuing (git rebase --continue
). This should be rare as Weblate typically associates one file edit per commit. Thedeconflict_add_continue.py
step could be extended to handle several files, but we’ve not yet done so. Note also the scriptshow_conflicts.py
which takes a file name as an argument and prints a side-by-side view of any git conflicts in the file.Restore changes that may have been lost from SVN, e.g. newly updated source strings or source line numbers, by running
msgmerge
on all pot files using the scriptupdate_pkg_po.py
.
Administrators
Currently @daroczig and @MichaelChirico have admin access to Weblate, both in the web UI and via SSH.
In case of any questions, reach out to them on the R-devel Slack’s #core-translation
channel, pinging both their usernames.
To add a new admin in the Weblate app:
- Go to the Users page
- Select the user
- Go to “Groups”
- Add to the “The R Project for Statistical Computing / Administration” group
To grant SSH access:
- Share your public key with one of the admins, who will add that to
~/.ssh/authorized_keys
- Ask for the SSH config from one of the admins
Team leaders
There are language teams defined in each project with the below extra permissions:
- Manage glossary
- Review strings
- Manage translation memory
See the general Weblate FAQ on how to manage members.