Goal: Replace the Weblate fork with a standalone plugin package cppa-weblate-plugin
installed on top of upstream Weblate from PyPI. Once complete, the fork repo is retired
and upstream Weblate + this plugin is installed. The package repository must meet
Alliance minimum engineering standards.
Verdict: GO
Every required capability maps to an officially documented Weblate extension point from Customizing Weblate. No fork, no monkey-patching, no modification to upstream source is needed.
- Custom file format (
quickbook.py) — subclassesweblate.formats.base.BaseFormatand registers viaWEBLATE_FORMATS. This is the explicit documented mechanism; see Appendix C for a working skeleton. - Custom Django app (
boost_endpoint) — added toINSTALLED_APPSviaWEBLATE_ADD_APPS. Standard Django URL routing serves the endpoints; no internal patching needed. - Package installation —
pip install git+https://…@<tag>alongside upstream Weblate from PyPI, exactly as the docs endorse for third-party packages. - Settings injection — a single override file at
/app/data/python/customize/settings.py, copied into the image with oneCOPYline; noweblate-dockersource changes required.
The only structural risk is internal API churn: Weblate's docs warn that internal interfaces may change without notice. Both the format subclass and the Django app touch only the public BaseFormat ABC and standard Django APIs, so exposure is minimal. Version pinning keeps this within normal team discipline — see Appendix B: Risk Register. Four alternatives were evaluated and rejected — see Appendix A: Alternatives Considered.
cppa-weblate-plugin/
├── LICENSE ← GPL-3.0-or-later (aligned with upstream Weblate)
├── README.md ← package overview, quickstart, architecture section, config reference
├── settings-override.py ← CD copies into image build context
├── docs/
│ ├── deployment-runbook.md ← install, rollback, health checks
│ └── boost-endpoint-api.md ← OpenAPI / REST reference for /boost-endpoint/
├── pyproject.toml ← declares ALL runtime deps
├── .github/
│ └── workflows/
│ ├── ci.yml ← pytest, ruff, coverage gate
│ └── integration.yml ← Weblate + package smoke tests
├── tests/
│ ├── formats/
│ │ └── test_quickbook.py ← QuickBook parser unit tests
│ └── endpoint/
│ ├── test_views.py ← boost_endpoint request/response tests
│ └── test_services.py ← BoostComponentService unit tests
└── src/boost_weblate/
├── __init__.py
├── formats/
│ ├── __init__.py
│ └── quickbook.py ← subclass of weblate.formats.base.BaseFormat; registered via WEBLATE_FORMATS += in settings-override.py
├── utils/
│ ├── __init__.py
│ └── quickbook.py ← QuickBook utilities
└── endpoint/ ← boost_endpoint Django app; loaded via INSTALLED_APPS / WEBLATE_ADD_APPS
├── __init__.py
├── apps.py ← AppConfig (name = "boost_weblate.endpoint")
├── urls.py
├── views.py
├── serializers.py
└── services.py
| Requirement | Standard |
|---|---|
| Test coverage | ≥ 90 % (enforced by --cov-fail-under=90) |
| CI | GitHub Actions: pytest, ruff check, coverage gate on every PR |
| Integration test | CI job: uv pip install weblate "git+https://…/cppa-weblate-plugin@HEAD" in a Docker-compose stack → smoke-test endpoint + format registration |
| CD | CD workflow clones upstream weblate-docker, edits its Dockerfile (install upstream Weblate from PyPI + plugin from git+https://…/cppa-weblate-plugin@<tag>, COPY settings-override.py into the image). settings-override.py is versioned in this repo. WEBLATE_ADD_APPS adds boost_weblate.endpoint to INSTALLED_APPS; WEBLATE_FORMATS += registers the QuickBook format class. |
| LICENSE | GPL-3.0-or-later in repo root (same license family as Weblate) |
| Runtime deps | All deps declared in pyproject.toml; no implicit system deps without docs |
| Documentation | README (quickstart + architecture section), deployment runbook, REST API reference |
| Reproducible install | uv pip install "git+https://…/cppa-weblate-plugin@<tag>" works against upstream Weblate PyPI package |
Weekly outcome: A cloneable package with QuickBook fully done: implementation, tests, and ≥ 90 % coverage on formats/quickbook.py and utils/quickbook.py; placeholder CI workflows only; URL registration approach for boost_endpoint resolved and documented.
- Create
cppa-weblate-pluginwithpyproject.toml(build backend:uv_build),LICENSE,.github/stub. - Add placeholder workflows
ci.ymlandintegration.yml(empty or minimal) so PRs have CI targets from day one. - Write
README.md: overview, quickstart, architecture (Weblate → QuickBook → boost-endpoint → boost-docs-translation), config reference for QuickBook /WEBLATE_FORMATSandsettings-override.pycopy semantics (upstreamweblate-docker+DockerfileCOPYat deploy tag). Defer boost-endpoint env vars andWEBLATE_ADD_APPSto Week 2 when that app ships. - Declare all runtime dependencies in
pyproject.toml(Weblate pin, any others). - Add root
settings-override.pywithWEBLATE_FORMATS +=only this week. Document the CD clone-and-patch flow for this file; endpoint-related settings land in Week 2.
- Implement
formats/quickbook.pyas a subclass ofweblate.formats.base.BaseFormatandutils/quickbook.py(fresh code; fork is reference only). - Register the format class in
settings-override.pyviaWEBLATE_FORMATS += ("boost_weblate.formats.quickbook.QuickBookFormat",). - Add unit tests (round-trip, edge cases) until
formats/quickbook.pyandutils/quickbook.pymeet ≥ 90 % coverage (same gate as Quality Baseline).
The minimal format handler skeleton and settings registration snippet are in Appendix C: Prototype — Minimal Plugin.
Weblate's urls.py does not auto-discover URL patterns from INSTALLED_APPS; it hardcodes include() calls gated by if "app" in settings.INSTALLED_APPS for known apps only. The plugin must register its own URLs via one of:
AppConfig.ready()— programmatically append tourlpatternsat startup (simplest for a self-contained plugin), orROOT_URLCONFoverride insettings-override.py— point to a custom URL conf that imports Weblate's patterns and adds the plugin'sinclude().
Action: prototype both approaches in the integration test environment and pick the one that survives Weblate upgrades best. Document the chosen approach in README.md so Week 2 implementation can proceed without ambiguity.
Weekly outcome: boost_endpoint fully implemented and tested; PR ci.yml enforces ruff + pytest + coverage; docs/boost-endpoint-api.md matches shipped endpoints; README updated for endpoint configuration.
boost_endpoint — complete in this week
- Implement
src/boost_weblate/endpoint/in full (fresh code; fork is reference only):AppConfig, URLs, views, serializers, and services for all REST endpoints. - Load the app by setting
WEBLATE_ADD_APPS=boost_weblate.endpoint(Docker env var) or adding"boost_weblate.endpoint"toINSTALLED_APPSinsettings-override.py. - URL registration: use the approach resolved in Week 1 (see above).
- Add a request/response test for every endpoint. Document
WEBLATE_ADD_APPSand endpoint URLs inREADME.md.
The AppConfig, URL config, and settings registration skeletons are in Appendix C: Prototype — Minimal Plugin.
- Write
docs/boost-endpoint-api.md: REST contract for/boost-endpoint/(aligned with the endpoints shipped above). - Implement
ci.yml:ruff check,pytest --cov --cov-fail-under=90, coverage upload.
Weekly outcome: integration.yml is fully working (no partial phases); docs/deployment-runbook.md written once to match the live CD path; CD script and release tag; fork retired.
- Replace the
integration.ymlplaceholder with a finished workflow: Docker Compose stack,uv pip install weblate "git+https://…/cppa-weblate-plugin@HEAD", smoke-test format registration and/boost-endpoint/.
- Document
WEBLATE_ADD_APPS=boost_weblate.endpointand fullsettings-override.pywiring in the example environment file anddocs/deployment-runbook.md. No manual volume steps. Include install, env vars, health checks, and plugin-tag rollback (pin CD to previous tag, redeploy). Validate the runbook against the CD stack you ship in this week’s PR so operator steps match the built image and compose layout. - CD script: clone upstream
weblate-docker, editDockerfile: replace"/app/boost-weblate[${WEBLATE_EXTRAS}]"with PyPIweblate[${WEBLATE_EXTRAS}], adduv pip install "git+https://…/cppa-weblate-plugin@<tag>",COPYsettings-override.pyfrom the plugin checkout (e.g./app/data/settings-override.py), then build and deploy. Document plugin tag/ref in the runbook; add Renovate or equivalent on the CD side if desired. - Tag & version: Git tag uses the format
boost-plugin-vX.Y.Z(e.g.boost-plugin-v1.0.0). The Python package version inpyproject.tomlis the PEP 440-compatibleX.Y.Z(e.g.1.0.0). The CD script pins the plugin via the git tag;pip/uvsees the numeric version. Roll forward/rollback via CD script / deploy config (plugin tag). - Retire
boost-weblatefork after production verification; remove any install path still referencing it.
| Alternative | Why rejected |
|---|---|
| Continue maintaining the fork | Fork diverges from upstream on every Weblate release; upgrade cost grows over time; security patches must be cherry-picked manually; no path to using upstream Weblate from PyPI |
| Monkey-patch Weblate at runtime | Brittle against any internal refactor; not endorsed by Weblate docs; would fail C1 and C2 |
| Contribute QuickBook format upstream | QuickBook is Alliance-specific; upstream is unlikely to accept it; does not cover boost_endpoint |
| Custom Weblate Docker image with source edits | Still a fork problem in disguise; COPY-in override approach achieves the same result without source modification |
The standalone plugin approach is the only alternative that satisfies all six criteria.
| ID | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| R1 | BaseFormat ABC changes between Weblate releases, breaking QuickBookFormat |
Low — it is the public extension API | High | Pin Weblate version in pyproject.toml; integration CI installs both packages on every PR; bumping the pin is a deliberate, tested action |
| R2 | Weblate removes or renames WEBLATE_FORMATS / WEBLATE_ADD_APPS Docker env vars |
Very low — these are documented deployment knobs | High | Same pin + integration CI; if removed, settings-override.py COPY path is a fallback requiring only a Dockerfile edit |
| R3 | Django major-version upgrade inside Weblate breaks boost_endpoint views/serializers |
Low — Django REST patterns are stable across majors | Medium | Standard Django upgrade path applies; no Weblate-internal Django usage in the plugin |
| R4 | git+https://…@<tag> install fails in air-gapped or restricted network environments |
Medium — depends on deployment environment | Medium | Mirror the plugin package to an internal PyPI index if required; the install mechanism is standard pip and switchable |
| R5 | Scope creep requiring Weblate internal API access (e.g. signals, celery tasks) | Low given current requirements | High | Any new requirement that cannot be satisfied via documented extension points is a no-go trigger |
The minimal skeleton needed to register one translation format handler in Weblate. Confirms that BaseFormat subclassing and WEBLATE_FORMATS registration work end-to-end with no fork or monkey-patch.
# src/boost_weblate/formats/quickbook.py
from weblate.formats.base import BaseFormat
class QuickBookFormat(BaseFormat):
name = "QuickBook"
format_id = "quickbook"
monolingual = True
autodetect_extensions = [".qbk"]
def load(self, storefile, template_store):
# Parse QuickBook source into translation units
...
def save(self):
# Serialise translation units back to QuickBook
...# settings-override.py (snippet)
WEBLATE_FORMATS += ("boost_weblate.formats.quickbook.QuickBookFormat",)Acceptance check: install alongside upstream Weblate and verify the format appears in the Weblate admin UI under Formats.
The minimal skeleton needed to load boost_endpoint as a standard Django app via INSTALLED_APPS. Note: Weblate's urls.py does not auto-discover URL patterns from added apps — the plugin must register its own URLs (e.g. via AppConfig.ready() or a ROOT_URLCONF override).
# src/boost_weblate/endpoint/apps.py
from django.apps import AppConfig
class BoostEndpointConfig(AppConfig):
name = "boost_weblate.endpoint"
label = "boost_endpoint"
verbose_name = "Boost documentation translation API"# src/boost_weblate/endpoint/urls.py
from django.urls import path
from . import views
app_name = "boost_endpoint"
urlpatterns = [
path("components/", views.ComponentListView.as_view(), name="component-list"),
path("components/<str:project>/<str:component>/", views.ComponentDetailView.as_view(), name="component-detail"),
]# settings-override.py (snippet)
INSTALLED_APPS += ["boost_weblate.endpoint"]
# or via Docker env var: WEBLATE_ADD_APPS=boost_weblate.endpointAcceptance check: install alongside upstream Weblate and confirm /boost-endpoint/components/ returns HTTP 200.