Scanning & CI
A scan is how your repo’s current state reaches Dialecto: the surgical slice, which is your catalog files plus optional usage snippets. There are two ways for it to arrive. By default the Dialecto GitHub App reads your translation files itself, at the paths you confirmed, and nothing needs wiring. If you block read access on a project, or you want usage snippets (which only your CI can send), the scan runs on your infrastructure as one documented HTTP call, simple enough to wire into any CI in a few lines. This page is that contract. A packaged GitHub Action for it is planned but does not exist yet.
The request
POST /api/repos/:repo_id/scans
Authorization: Bearer <scan token>
Content-Type: application/json
The scan token is per-repo, shown once at creation, stored only as a hash, and regenerable from the repo’s settings. It authorizes scanning — nothing else.
Payload
| Field | Required | Description |
|---|---|---|
gitSha | yes | the commit the scan describes |
gitBranch | yes | the branch — scans are branch-aware |
checksum | yes | idempotency key; resending the same checksum is a no-op |
templates | yes | array of {path, content} — your translation files (.po/.pot, or .json for a JSON project), verbatim bytes |
gettextUsages | no | array of {file, content, start_line, end_line} — source snippets around gettext calls |
Paths must be relative paths inside the repository (no ..) that
match the project’s confirmed translation file paths; locale, domain,
and template-vs-catalog are derived from the path. For a Phoenix app
that means sending priv/gettext/** as-is after
mix gettext.extract --merge. JSON projects send their catalog files
the same way; the JSON guide covers what is
different, including the Astro add-on that sends where your code reads
each key.
Limits: 1,000 template files, 5 MB per file, 10 MB request body, 50,000 usage snippets.
Responses
| Status | Meaning |
|---|---|
201 | ingested — body includes entry/usage counts |
200 | idempotent replay (same checksum), nothing changed |
401 | bad or missing scan token |
413 | over a size limit |
422 | a template failed to parse — the whole scan is rejected, nothing partial is stored |
Parsing is strict on purpose: one malformed catalog rejects the scan,
so the index never holds a half-truth. Validate with msgfmt --check
locally if a 422 surprises you.
Asking what to scan
GET /api/repos/:repo_id/scan-config
Authorization: Bearer <scan token>
returns {scan_mode, branch_regex, tracked_branches} — so a CI job
can decide cheaply whether the current branch should scan at all.
scan_mode is a repo setting: selected (default — the branches you
track) or all (with an optional branch regex filter).
What a scan triggers
Ingesting a scan fans out the follow-up work automatically: translation-memory capture from newly translated strings, re-publishing the merge gate and quality checks on affected PRs, and — if the repo opted in — the auto-translate pass over newly untranslated strings.
A CI recipe
Any CI works; the job is: check out, extract, build the payload, POST.
As a GitHub Actions sketch using only git, jq and curl, which the
runner image already has (adjust the paths to your catalogs):
- uses: actions/checkout@v6
- run: mix gettext.extract --merge
- name: Send the scan
env:
DIALECTO_SCAN_TOKEN: ${{ secrets.DIALECTO_SCAN_TOKEN }}
DIALECTO_URL: https://app.dialecto.eu
DIALECTO_REPO: "<your repo id>"
run: |
set -euo pipefail
paths=(':(glob)priv/gettext/**/*.po' ':(glob)priv/gettext/**/*.pot')
git ls-files -z -- "${paths[@]}" | while IFS= read -r -d '' file; do
jq -cn --arg path "$file" --rawfile content "$file" \
'{path: $path, content: $content}'
done > "$RUNNER_TEMP/templates.jsonl"
checksum=$({ printf '%s\n%s\n' "$GITHUB_SHA" "$GITHUB_REF_NAME"; \
git ls-files -s -- "${paths[@]}"; } | sha256sum | cut -d' ' -f1)
jq -s --arg sha "$GITHUB_SHA" --arg branch "$GITHUB_REF_NAME" \
--arg checksum "$checksum" \
'{gitSha: $sha, gitBranch: $branch, checksum: $checksum, templates: .}' \
"$RUNNER_TEMP/templates.jsonl" > "$RUNNER_TEMP/scan.json"
curl -sS --fail-with-body --retry 3 -X POST \
-H "Authorization: Bearer $DIALECTO_SCAN_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "@$RUNNER_TEMP/scan.json" \
"$DIALECTO_URL/api/repos/$DIALECTO_REPO/scans"
The checksum covers the commit, the branch and every sent file’s blob
id, so a re-run is a no-op and a revert still counts as a new scan.
app.dialecto.eu is the address the app will have when it opens; until
then, early-access accounts are told the address to use. If you run
Dialecto yourself, use your own host.
Rate limiting applies to the API (HTTP 429 with a retry-after
header); one scan per push is the intended cadence.
The source-rewrite action
Separate from scanning: when a Dialecto PR carries
call-site rewrites, a repo-side action applies
them against your working tree, touching only the rewrites listed in
the PR. Dialecto computes those rewrites today. The action,
dialecto-eu/source-rewrite, is public on GitHub at
github.com/dialecto-eu/source-rewrite.
Reference it as dialecto-eu/source-rewrite@v1, or pin it to a release
commit. In a repository without the action, the PR lists every call site
as a manual follow-up instead.