transLATER
This page was translated automatically from Italian. Read the original
Write now, translate LATER.
A service that translates your GitLab project documentation by itself, using a language model that runs at your home: no external services, no costs per word. Write in your language, push, and a few minutes later the translations are in the repository. The name plays on the English word later: you write now, the translation arrives a little later.
It translates:
- Markdown pages: README, documents, Hugo site pages (including front matter), and in code blocks, the comments (
# …,// …) and the labels of drawn diagrams in text; - SVG drawings: only the labels, the rest of the file remains identical; if a translated label does not fit, the font shrinks just enough;
frontend.yml(showcase site tabs) and website labels (i18n/it.toml);- example configuration files (
.env.example,*.yaml,*.toml,*.conf,*.ini…): only the comments, keys and values remain identical.
The translations end up next to the originals, in the same repository:
| Original | Translation |
|---|---|
README.md | README.en.md |
docs/img/schema.svg | docs/img/schema.en.svg |
content/guide/x/index.md (Hugo) | content/guide/x/index.en.md |
frontend.yml | frontend.en.yml |
i18n/it.toml | i18n/en.toml |
.env.example, conf.yaml (comments) | .env.en.example, conf.en.yaml |
At the top of every Markdown, including the original, there is the available languages bar: 🌐 **Italiano** · [English](https://git.eandgcode.com/egprojects/translater/-/blob/main/README.en.md). It is written by checking which files actually exist, so an added language appears on its own; in translations, links to other documents and images point to their translations.
GitLab ──push──► webhook ──► transLATER (queue, one run at a time) ──► commit «Automatic translations»
│
└──► translater-ollama (language model on GPU)
Requirements
- A server with Docker and the
docker composeplugin (Linux). - An NVIDIA graphics card with Docker support (NVIDIA Container Toolkit): recommended. With 12 GB of video memory, the default model stays about 80% on the card and writes approximately 43 tokens per second. Without a graphics card, it runs on the processor, much slower, and requires about 20 GB of free RAM.
- Disk: approximately 31 GB (18.7 GB for the model, 9.4 GB for the Ollama image, 2.2 GB for the transLATER image with Chromium inside).
- A GitLab instance (even self-hosted) reachable from the server, and the service reachable from GitLab for the webhook. GitHub is not supported for now.
While working, the model takes up almost the entire graphics card and a few GB of RAM; after 5 minutes of inactivity, it unloads itself, and between one run and the next, only the two programs remain (about a hundred MB). If another program is using the graphics card, the model relies more on the processor: slower, but it works.
Installation
- The GitLab user (see below): a normal user, Maintainer of the projects to be translated, and their token.
- On the PC, in this folder:
./setup-translater.sh. On the first run, it creates.envfrom the.env.en.examplemodel: fill in the server details (SERVER_SSH,SERVER_KEY),GITLAB_URLandGITLAB_TOKEN, then run it again. The script copies itself to the server, builds the image, starts the two containers, and downloads the model (once). It can also be run directly on the server: in that case, theSERVER_*lines remain empty. - In every project to be translated, a
translations.yml. On the next run, the service finds it, creates the project webhook, and translates everything; then it starts again on every push.
The status page is at http://IP-DEL-SERVER:8790/ (user translater, password in .env on the server: STATO_PASSWORD): queue, latest runs, PNG previews of translated drawings, «Write now, translate LATER» button.
The GitLab user
- A normal user (non-administrator), for example
translater, with Maintainer membership in the projects to be translated (Manage → Members): this is required for pushing to the main branch and for creating the webhook. - A personal access token with
apiandwrite_repositoryscopes: this goes intoGITLAB_TOKEN. - If GitLab and transLATER are on the same home network, GitLab does not call local addresses by default: in Admin → Settings → Network → Outbound requests, add
IP-DEL-SERVER:8790to the allowlist (for example192.168.1.20:8790).
transLATER commits carry that user’s «noreply» address, and the webhook recognizes them: a translation commit does not trigger another run.
The model
By default gemma4:26b-a4b (Google Gemma 4, experts: 26 billion parameters, 4 active at a time), chosen through a competition on the same texts: sections with lists, a paragraph with code, a page’s front matter, the 36 labels of an SVG diagram, and a few difficult sentences.
| Model | Speed (RTX 4070 12 GB + Xeon 22 core) | How it went |
|---|---|---|
| gemma4:26b-a4b (18.7 GB) | 43 token/s | excellent: glossary respected, bold text and code intact, in the drawing it moves words between broken lines so they remain readable |
| qwen3.6:35b-a3b (22.6 GB) | 48 token/s | good, but it also translates inside the code (sensors/<stanza>/… → <room>) and applies the glossary incorrectly |
| gemma4:31b (dense, 20.4 GB) | 9 token/s | like the 26b, five times slower, and it also touches the code |
| translategemma:12b | 6.5 token/s (CPU only) | good but without context: «si toglie tensione» → «we are relieving stress» |
| translategemma:27b | 5.3 token/s | no better than the 12b |
It changes with --model <nome> (any Ollama model): the cache is per model, so with a new model everything is retranslated. On smaller machines gemma4:12b (8 GB) or gemma4:e4b (6.6 GB) are lighter; their quality has not been tested.
How it translates
- Section-based Markdown: each request is an entire section (a heading and its paragraphs), using the document title and the last translated part as context, along with the project glossary. Code within backticks, addresses, and shortcodes remain untouched.
- Code blocks: in blocks with a language (
bash,yaml,ini,cpp…) only the comments are translated; commands, code, and quoted strings remain as they are. In blocks without a language, usually diagrams drawn with─ │ ┌ ▶, the labels between lines are translated, and what follows on the same line remains in its column: a longer label uses the following spaces or shortens the connecting line, and if it doesn’t fit, it remains as it was. - Configuration files: the translated copy (
.env.en.example) has comments in the new language and is otherwise identical to the original; translated Markdown files link to it instead of the original. - Verification: a translation is only accepted if it has the same line-by-line structure (headings, list items, table rows) and the same codes and addresses. If not, it tries again, first block by block, then with placeholders; only at the end does a piece remain in the source language (the log explains why) and the next run tries again.
- SVG drawings: the labels in a drawing are translated together (the model knows that two consecutive lines can be a broken sentence). Then Chromium measures each label and the space available: the bounding box containing it in the original drawing (with the same margin from the edge) or, if none exists, the longest line in its column. Only if the translation does not fit is the font size reduced, down to 70%.
- Links between documents: in translated files, links to other translated files point to the translations (
docs/guida.md→docs/guida.en.md, including the anchor); if the link text is the filename, that is also changed. Actual text (“the guide”) remains translated as is. - Cache: every translated section remains in
dati/cache.sqliteon the server; a changed document only costs the changed sections. - Manual correction: you write the translated file yourself (e.g.,
README.en.md) without the first line<!-- traduzione automatica … -->: yours wins, transLATER will not touch it again. - Hugo sites: in translated pages, links to site sections (
/guide/x/) become those of the language (/en/guide/x/); Hugo automatically selects the translated drawings of a page (schema.en.svg) for the English page.
When it runs
- On every push to the main branch of a project containing
translations.yml(webhook). - Every
SCOPERTA_MINUTI(usually one hour): it searches for new projects and those with commits not yet translated, in case a webhook was missed. - Manually:
./setup-translater.sh --translate gruppo/progetto(orall), or from the status page.
The script: setup-translater.sh
| Command | What it does |
|---|---|
(none) or --install | .env, image, startup, model download |
--translate <gruppo/progetto|all> | puts in queue immediately |
--count <gruppo/progetto> | how many files and pieces need translating, without translating |
--test <file.md|file.svg> [lingua] | from the PC: translates a file and places it next to it (with PNG previews for SVGs) |
--status · --logs | queue and latest runs · follows the log |
--model <nome> | changes model |
--start · --stop · --restart · --update · --uninstall | the containers |
License
MIT, see LICENSE. The base model has its own license (Gemma Terms of Use).
What do you think?
Selected comments
A selection of the comments we received, published with their authors' permission. The average counts every rating.