Skip to content

build(docker): compile translation catalogs into the runtime image - #606

Open
hamza-56 wants to merge 1 commit into
i18n-es-fr-catalogsfrom
i18n-docker-compile
Open

build(docker): compile translation catalogs into the runtime image#606
hamza-56 wants to merge 1 commit into
i18n-es-fr-catalogsfrom
i18n-docker-compile

Conversation

@hamza-56

@hamza-56 hamza-56 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Part of: Sparkth UI, emails, API errors, and AI-generated content are English-only

Layer 5 of the i18n stack (#601#602#603#604#606#607).

What

Compiles the translation catalogs into the published Docker image at build time, so a production deployment (docker-compose.prod.yml pulling ghcr.io) serves the translations without any operator step.

Changes

  • build(docker): a catalog-builder stage compiles the committed .po catalogs with the lockfile-pinned dev environment (pybabel is a dev-group dependency that must not enter the runtime image); the runtime stage copies only sparkth/locale, now holding the compiled .mo files
  • docs: translations guide documents build-time compilation; native deployments still run make i18n.compile themselves

How to Test

  1. docker build --target catalog-builder . — the build log shows compiling catalog sparkth/locale/{es,fr}/LC_MESSAGES/messages.po (verified locally, exit 0)
  2. Full image: make docker.build, then run it and curl -s -X POST localhost:8000/api/v1/auth/login -H 'Content-Type: application/json' -H 'Accept-Language: es' -d '{"username":"ghost","password":"nope"}'{"detail":"Usuario o contraseña incorrectos"}

Notes

No new dependency and no runtime-image size change beyond the two .mo files; the dev group is installed only in the throwaway stage. docker-publish.yml builds on push to main/tags, so PR CI does not exercise the Dockerfile; step 1 above is the local verification.

This PR description was written with the assistance of an LLM (Claude).

The runtime loads compiled .mo files, which are git-ignored build artifacts;
without this the published image served English only. pybabel is a dev-group
dependency that must not enter the runtime image, so a throwaway catalog
stage compiles with the lockfile-pinned dev environment and the runtime stage
copies only sparkth/locale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hamza-56

Copy link
Copy Markdown
Contributor Author

@claude-review

@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Claude finished @hamza-56's task in 2m 24s —— View job


Review: compile translation catalogs into the runtime image

I traced the full build graph and the runtime translation-loading path. This is a clean, correct change — approving in spirit (I can't submit a formal approval).

  • Read PR diff (Dockerfile + translations.md)
  • Analyze full Dockerfile / build graph
  • Verify runtime load path & dev-group isolation
  • Post review

Why it's correct

  • The .mo files land exactly where the app reads them. LOCALE_DIR resolves to Path(__file__).resolve().parents[2] / "locale" (sparkth/core/i18n/translate.py:34). With uv's editable project install, sparkth resolves to /app/sparkth, so the app reads /app/sparkth/locale — precisely the directory the runtime overlays from catalog-builder (Dockerfile:63). ✅
  • The dev group never reaches the runtime image. Runtime copies /app (incl. .venv) from builder (built with --no-dev), and pulls only sparkth/locale from catalog-builder. The uv sync --frozen that installs babel mutates catalog-builder's venv, which is discarded. Confirmed babel>=2.17 is a dev-group dep (pyproject.toml:62), so uv sync --frozen (default groups) makes pybabel available. ✅
  • The overlay is a strict superset merge. catalog-builder is FROM builder, so its sparkth/locale already has the committed .po files plus the freshly-compiled .mo — copying it over the builder copy only adds files, never removes. ✅
  • .mo is gitignored (.gitignore:54,252), so build-time compilation is genuinely required; the docs note native deployments still run make i18n.compile, matching the recipe (uv run pybabel compile -d sparkth/locale). ✅

Non-blocking notes

  • Low — plugin catalogs aren't compiled. pybabel compile -d sparkth/locale (and the runtime overlay of only sparkth/locale) covers core's catalog only. The translations guide documents that a plugin ships catalogs by registering its own dir on LOCALE_DIRS (sparkth/lib/i18n.py docstring). No plugin ships a .po today, and this exactly mirrors make i18n.compile, so there's no regression — just a latent limitation to remember when the first plugin ships translations. Worth a one-line comment near Dockerfile:48 so the next person knows the scope is core-only.
  • Nit — column alignment. The existing COPY --from=builder / --from=frontend-builder lines pad --from= to a fixed column; the new COPY --from=catalog-builder line (Dockerfile:63) is longer and breaks that alignment. Purely cosmetic.
  • Test coverage. No automated test, as expected for a Dockerfile change, and docker-publish.yml doesn't build on PRs — so this rests on the author's local curl verification (Accept-Language: es → Spanish 401 body). Reasonable given the constraints; can't realistically be unit-tested.

Verdict

Correct, well-reasoned, and the multi-stage split is exactly the right pattern for keeping pybabel out of the runtime while staying lockfile-pinned. No changes required to merge.
· i18n-docker-compile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant