> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tolk.every-day.io/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Action

> Automatically get a pull request with new translations as soon as a language is fully translated.

With a GitHub Action, new translations arrive in your repository as a pull request on their own. The trigger is the `language.completed` webhook: Tolk sends it as soon as a language is 100% translated. The workflow also runs on a schedule and by hand, so nothing is left behind even without the webhook.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant T as Tolk
    participant R as Relay
    participant G as GitHub
    T->>R: POST webhook language.completed (signed)
    R->>R: Verify signature
    R->>G: POST /repos/{repo}/dispatches (tolk-language-completed)
    G->>T: GET /v1/exports/{profile} (with ETag)
    G->>G: Open or update pull request
```

GitHub starts a workflow from outside with a `repository_dispatch`. That requires a GitHub token and its own format, so Tolk cannot call the GitHub API directly. A small **relay** in between verifies Tolk's signature and starts the workflow. Without a relay the workflow works too, just only on the schedule and by hand.

## 1. Token in GitHub

<Steps>
  <Step title="Create a token in Tolk">
    Under **Settings › API tokens** (*Instellingen › API-tokens*), create a token with **Read only** (*Alleen lezen*), named "GitHub Actions".
  </Step>

  <Step title="Secret in GitHub">
    Add the token to the repository (or the organization) as the secret `TOLK_TOKEN`.
  </Step>

  <Step title="Allow pull requests">
    In the repository, under **Settings › Actions › General**, turn on *Allow GitHub Actions to create and approve pull requests*.
  </Step>
</Steps>

## 2. The workflow

Put this file in `.github/workflows/tolk-pull.yml`. Set `TOLK_PROFILE` to the export profile you want to fetch (`GET /v1/project` lists them under `exportProfiles`).

```yaml .github/workflows/tolk-pull.yml theme={null}
name: Fetch Tolk translations

on:
  repository_dispatch:
    types: [tolk-language-completed]
  schedule:
    - cron: '0 6 * * 1-5' # weekdays at 06:00 UTC
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write

concurrency:
  group: tolk-pull
  cancel-in-progress: false

jobs:
  pull:
    runs-on: ubuntu-latest
    env:
      TOLK_API: https://api.tolk.every-day.io/v1
      TOLK_PROFILE: laravel-json
      TOLK_TOKEN: ${{ secrets.TOLK_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - name: Fetch export
        run: |
          set -euo pipefail
          fetch() {
            curl -sS -o export.bin -D headers.txt -w '%{http_code}' \
              -H "Authorization: Bearer $TOLK_TOKEN" \
              -H 'Accept: application/json' \
              "$TOLK_API/exports/$TOLK_PROFILE"
          }
          status=$(fetch)
          if [ "$status" = "202" ]; then
            job=$(jq -r '.data.id' export.bin)
            for attempt in $(seq 1 60); do
              sleep 5
              state=$(curl -sS -H "Authorization: Bearer $TOLK_TOKEN" "$TOLK_API/jobs/$job" | jq -r '.data.status')
              [ "$state" = "succeeded" ] && break
              [ "$state" = "failed" ] && { echo "The export failed."; exit 1; }
            done
            status=$(fetch)
          fi
          [ "$status" = "200" ] || { echo "Tolk responded $status"; cat export.bin; exit 1; }
          if grep -qi '^content-type: application/zip' headers.txt; then
            unzip -o -q export.bin -d .
          else
            echo "Expected a zip. Choose a profile with multiple languages or files."; exit 1
          fi
          rm -f export.bin headers.txt

      - name: Open pull request
        uses: peter-evans/create-pull-request@v7
        with:
          branch: tolk/translations
          commit-message: 'chore(i18n): update translations from Tolk'
          title: 'chore(i18n): update translations from Tolk'
          body: |
            New translations from Tolk (profile `${{ env.TOLK_PROFILE }}`).
            ${{ github.event.client_payload.locale && format('Trigger: `{0}` is fully translated.', github.event.client_payload.locale) || '' }}
          delete-branch: true
```

* If there are no changes, the action opens no pull request.
* An existing pull request on `tolk/translations` is updated, so you never get more than one at a time.

<Tip>
  As soon as the [Laravel package](/developers/laravel-package) is available, replace the *Fetch export* step with `composer install` and `php artisan tolk:pull`.
</Tip>

## 3. The relay for `language.completed`

The relay is a small endpoint, for example a serverless function. It verifies Tolk's signature and starts the workflow. The example uses Node 20 or newer.

```javascript relay.mjs theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';
import { createServer } from 'node:http';

const { TOLK_WEBHOOK_SECRET, GITHUB_TOKEN, GITHUB_REPOSITORY } = process.env;

function isFromTolk(headers, body) {
  const timestamp = Number(headers['x-tolk-timestamp']);
  const signature = String(headers['x-tolk-signature'] ?? '');
  if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > 300) {
    return false;
  }
  const expected = 'sha256=' + createHmac('sha256', TOLK_WEBHOOK_SECRET)
    .update(`${timestamp}.${body}`)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

createServer(async (request, response) => {
  const chunks = [];
  for await (const chunk of request) chunks.push(chunk);
  const body = Buffer.concat(chunks).toString('utf8');

  if (!isFromTolk(request.headers, body)) {
    response.writeHead(401).end();
    return;
  }

  const payload = JSON.parse(body);
  if (payload.event === 'language.completed') {
    await fetch(`https://api.github.com/repos/${GITHUB_REPOSITORY}/dispatches`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${GITHUB_TOKEN}`,
        Accept: 'application/vnd.github+json',
        'X-GitHub-Api-Version': '2022-11-28',
      },
      body: JSON.stringify({
        event_type: 'tolk-language-completed',
        client_payload: { project: payload.project.slug, locale: payload.data.language.locale },
      }),
    });
  }

  response.writeHead(204).end();
}).listen(process.env.PORT ?? 8080);
```

| Variable | Value |
| - | - |
| `TOLK_WEBHOOK_SECRET` | The secret of the webhook in Tolk |
| `GITHUB_TOKEN` | A fine-grained token with access to this repository only and the permission *Contents: Read and write* |
| `GITHUB_REPOSITORY` | For example `everyday-nl/webshop` |

Then, in Tolk under **Settings › Webhooks**, add a webhook with the URL of the relay and the `language.completed` event. Use **Send test** (*Test versturen*) to check that the relay is reachable: a `ping` starts no workflow, but the relay responds `204`.

<Warning>
  The relay responds within a few seconds. Tolk waits at most 5 seconds for a response and then retries. A duplicate delivery is harmless: the workflow then updates the same pull request.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.