vienalatina/.woodpecker.yml
Claude 986c066ae3
Replace DeepL with a self-hosted translation engine
The DeepL free tier is metered (it failed in production with HTTP 456
Quota exceeded) and would require every deployment of this platform to
carry its own API account. Translation now runs on M2M100 418M (MIT) via
CTranslate2, shipped inside the pipeline image: no key, no quota, and no
content or visitor data leaving the server.

Also restores the multi-source behaviour of the original WordPress plugin,
which the Python port had narrowed to Spanish-only. Any of the three site
languages can now be the authored original.

Because any language can be a source, loop prevention is no longer
structural and is now explicit: generated siblings carry `translated_from`
and are never treated as sources, and the bot's own [skip-translate]
commits are skipped outright (that marker was already being written but
never read).

Markup protection moves in-process now that DeepL's tag_handling=html is
gone. Code blocks and raw HTML pass through untouched; link targets,
inline code and protected community terms are masked with placeholders
that are verified to survive the round trip, failing the pipeline rather
than shipping corrupted text.

Two fixes along the way:

- Generated siblings no longer inherit the source's `slug`. They did,
  which meant the first retranslation of a WordPress-migrated post moved
  /de/<german-slug>/ onto /de/<spanish-slug>/ and destroyed the inbound
  link preservation wp-to-hugo.py exists for.
- `manual_translation` now works from the CMS. Decap only ever exposed it
  on the source while the script read it on the target, so the toggle did
  nothing. It now means "hands off" on both sides.

wp-to-hugo.py marks migrated Polylang siblings frozen, since those are
human translations and regenerating them would replace them with weaker
machine output.

Adds --backfill for sources missing siblings, which also fixes the
existing 404s on /de/page/acerca/ and /pt-br/page/contacto/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NizVpJ2dwzCbjCrTLCjeHn
2026-09-14 16:34:25 +00:00

48 lines
1.6 KiB
YAML

# Woodpecker CI: translate → build → deploy
#
# Triggered by the Gitea push webhook. The translate step replaces the old
# synchronous WordPress save_post hook: publishing in Decap is one git commit,
# translation happens here, asynchronously, and every generated sibling lands
# as a reviewable bot commit.
#
# Translation is self-hosted — the model ships inside vienalatina/translate,
# so there is no API key and no third-party request. Build that image on the
# server before the first run:
# docker build -t vienalatina/translate:1 docker/translate
#
# Secrets to configure in Woodpecker (repo settings → secrets):
# gitea_push_token — Gitea token for the translations bot user
# (translations@vienalatina.com) with repo write access
#
# The deploy step writes to the Caddy web root via a host mount, which
# requires the repo to be marked "trusted" in Woodpecker.
when:
- event: push
branch: main
steps:
translate:
image: vienalatina/translate:1
pull: false # built locally on the server, never fetched from a registry
environment:
GITEA_PUSH_TOKEN:
from_secret: gitea_push_token
commands:
- python scripts/translate.py
build:
image: hugomods/hugo:0.135.0
commands:
# Bundle Decap locally so the /admin page makes zero third-party requests.
- wget -q -O static/admin/decap-cms.js https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js
- hugo --minify
deploy:
image: alpine:3.20
volumes:
- /var/www/vienalatina.com:/deploy-target
commands:
- apk add --no-cache rsync
- rsync -a --delete public/ /deploy-target/