Weblate Component Sync: Git, PostgreSQL, and Celery Boundaries
Design Weblate around component boundaries, Celery worker health, and Git-to-database verification to prevent translation drift.
07 Nov 2025, 12:19 UTC

The Problem: Two Sources of Truth
Weblate does not edit translation files directly in Git. It imports strings from version control into PostgreSQL, lets translators edit through the Django UI, then exports finished strings back to Git. If the import and export paths drift, translators review stale strings or their work is overwritten by the next repository pull.
The useful takeaway is to treat the Weblate component as the trust boundary: PostgreSQL is a working cache for editing, and Git remains the authoritative record of translation files.
Requirements
- Git keeps the authoritative version history for translation files.
- PostgreSQL provides fast search, translation memory, and reviewer workflows.
- Changes in either system converge without silent data loss.
Smallest Suitable Design
A component maps one VCS repository and branch to one set of translation units, such as locale/*/LC_MESSAGES/messages.po. Weblate parses the files into relational tables so the UI can search and filter without re-reading Git on every keystroke.
Long-running work goes through Celery, a distributed task queue that runs jobs outside the HTTP request cycle. A webhook or manual update enqueues a task; a worker pulls the repository, parses changes, updates PostgreSQL, and later commits translations back to Git.
Trust and Data Boundaries
The component owns the VCS URL, branch, file mask, base language, and file format. Weblate trusts PostgreSQL for the current editing session, but trusts Git for the canonical file bytes. Think of the database as a materialized view of the repository, not the master record.
Because of this boundary, a successful UI save does not mean the change is in Git. It means the change is in the database until a separate export task runs.
Operational Checks
Verify worker health before assuming a component is current. Run inside the Weblate worker container, or with docker exec against the worker container:
# Required permissions: shell access as the weblate user or root # Placeholder: none; run against the local worker python -m celery -A weblate inspect active python -m celery -A weblate inspect stats
Expected check: a list of running tasks per worker and worker hostnames. Empty output or a broker connection error means workers are down or cannot reach Redis/RabbitMQ.
Practical synchronization test: push a trivial commit to the tracked repository, such as a whitespace change in a translation file. Watch the Weblate admin log for a components.update task. If the task does not appear within the configured sync interval, the worker is not consuming the queue.
Failure Modes
| Failure | Effect | Detection |
|---|---|---|
| Worker dies mid-parse | Database has partial strings; Git is unchanged | Component stuck on "Updating" |
| Export updates database but Git push fails | UI shows translated strings; Git still has old file | Out-of-sync warning in Weblate admin |
| Concurrent exports on one component | Lock contention or lost updates | Task timeouts in Celery logs |
| Message broker outage | No sync tasks run; UI appears frozen | celery -A weblate inspect ping returns no workers |
Conditions That Change the Design
- Millions of strings: initial parsing can exhaust worker memory. Split into more components or raise container memory limits.
- High push frequency: many webhooks per minute serialize behind one worker. Add workers or shard by component.
- External systems write to the same branch: Weblate exports can conflict with out-of-band commits. Use protected branches and pull requests instead of direct pushes.
Configuration Example
# Example placeholders in Weblate settings CELERY_TASK_ALWAYS_EAGER = False CELERY_WORKER_CONCURRENCY = 4 CELERY_WORKER_MAX_MEMORY_PER_CHILD = 200000
Edit settings.py inside the Weblate container as the weblate user, then restart workers. Risk: concurrency too high on a memory-constrained host causes out-of-memory kills during large file imports.
Verification
- Select a low-traffic component.
- Commit a test string change to the tracked Git repository.
- Run
python -m celery -A weblate inspect scheduledto confirm the update task is queued. - Open the component in Weblate and confirm the new string appears without a manual reload.
Limitations
Weblate locking prevents simultaneous exports on the same component, but it does not prevent a translator from editing a string that has already changed upstream. Check the component's out-of-sync status before starting a review pass.
Configuration changes alter runtime state. To revert, restore the previous settings.py values and restart the workers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.