Dokumentasjonsmeny

GitHub Actions

Automatic translations in your pull requests. Every PR gets translated, committed, and ready to merge.

Just looking for the action? localheroai/localhero-action on GitHub Marketplace →

Prefer a one-click install instead of editing a workflow file? Try the Localhero.ai GitHub App on the Marketplace →

Quick Start

Add this workflow to your repository. That's it.

Example workflow · .github/workflows/localhero-translate.yml
name: Translate

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review, unlabeled]
    paths:
      - "locales/**"  # Adjust to your path
      - "localhero.json"
  repository_dispatch:
    types: [localhero-sync]
  workflow_dispatch:

jobs:
  translate:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      # Checkout PR with history so we only translate what changed
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.client_payload.branch || github.head_ref }}
          fetch-depth: 0
          persist-credentials: false

      - uses: localheroai/localhero-action@v1
        with:
          api-key: ${{ secrets.LOCALHERO_API_KEY }}

Setup checklist

1. Create the workflow file above at .github/workflows/localhero-translate.yml

2. Add your API key to repository secrets as LOCALHERO_API_KEY

Repository → Settings → Secrets and variables → Actions → New secret

3. Update the paths: to match where your translation files live

Get your API key from your account.

How it works

When it runs

The workflow triggers when a PR changes your translation files. It compares your branch to the base branch, finds new or modified keys, translates them, and commits the results directly to your PR. See translations in the pull request for what that commit contains and how it affects CI.

What gets skipped

The action automatically skips translation when:

  • • PR has a skip-translation label
  • • PR is a draft
  • • Commit was made by the LocalHero bot (prevents loops)

Since drafts are skipped, the workflow above also runs when you mark a draft PR as ready for review. That is what ready_for_review in its types line does. Without it, GitHub only starts the workflow when a PR is opened, gets new commits or is reopened. Your translations would then wait for the next push. The unlabeled type does the same when you remove a skip label.

If your workflow file doesn't have this line yet, add it under pull_request: and keep your paths:. List all five types, since setting them replaces the GitHub defaults:

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review, unlabeled]

Permissions

contents: write lets the action commit translations. pull-requests: write allows adding PR comments.

Configuration

The action accepts these inputs:

Input Default Description
api-key required Your LocalHero API key
command ci CLI command: ci, translate, push, pull
verbose false Show detailed output
cli-version latest Pin to a specific CLI version
skip-labels skip-translation PR labels that skip translation (comma-separated)
working-directory . Directory holding localhero.json, relative to the repository root. Set it in a monorepo.
Example with options
- uses: localheroai/localhero-action@v1
  with:
    api-key: ${{ secrets.LOCALHERO_API_KEY }}
    verbose: true
    skip-labels: 'skip-translation,wip,no-i18n'

Common patterns

Gettext / PO files

For projects using gettext, run your message extraction before the action:

Example with Python/Django
steps:
  - uses: actions/checkout@v7
    with:
      ref: ${{ github.head_ref }}
      fetch-depth: 0
      persist-credentials: false

  - uses: actions/setup-python@v5
    if: github.actor != 'localhero-ai[bot]'
    with:
      python-version: '3.11'

  - name: Extract messages
    if: github.actor != 'localhero-ai[bot]'
    run: |
      pip install -r requirements.txt
      python manage.py makemessages -a

  - uses: localheroai/localhero-action@v1
    with:
      api-key: ${{ secrets.LOCALHERO_API_KEY }}

Adjust the extraction command for your framework: xgettext for C/C++, pybabel extract for Flask, etc.

Example with React/Lingui
steps:
  - uses: actions/checkout@v7
    with:
      ref: ${{ github.head_ref }}
      fetch-depth: 0
      persist-credentials: false

  - uses: actions/setup-node@v4
    if: github.actor != 'localhero-ai[bot]'
    with:
      node-version: '22'

  - name: Extract messages
    if: github.actor != 'localhero-ai[bot]'
    run: |
      npm ci
      npx lingui extract

  - uses: localheroai/localhero-action@v1
    with:
      api-key: ${{ secrets.LOCALHERO_API_KEY }}

Manual trigger

The workflow includes workflow_dispatch, so you can run it manually from the Actions tab.

Stacked pull requests

When one pull request builds on another, each one translates only the keys it adds or changes. The Action compares your branch with the branch your pull request targets, not with main. Keys from the pull requests below yours don't get translated twice. The baseBranch setting in localhero.json only applies to runs outside a pull request. This needs CLI 0.0.92 or later. The Action uses the latest version unless you pin one with cli-version.

  • • After the pull request below yours merges, yours gets a new target, usually main. Push a commit or close and reopen the pull request to translate against it. Re-running an old workflow run won't help. A re-run replays the original event, with a target branch that may already be deleted.
  • • If two pull requests in the stack both got new translations, a rebase can conflict in the locale files. Keep both sides. They are different keys.
Alternative: Using the CLI directly

For advanced use cases, you can call the CLI directly instead of using the action. This gives you full control but requires more setup.

name: Translate

on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review, unlabeled]
    paths:
      - "config/locales/**"
      - "localhero.json"
  repository_dispatch:
    types: [localhero-sync]
  workflow_dispatch:

concurrency:
  group: translate-${{ github.event.client_payload.branch || github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  translate:
    if: |
      github.event_name != 'pull_request' || (
        !contains(github.event.pull_request.labels.*.name, 'skip-translation') &&
        github.event.pull_request.draft == false &&
        !(github.actor == 'localhero-ai[bot]' && github.event.action == 'synchronize'))
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
    - uses: actions/checkout@v7
      with:
        ref: ${{ github.event.client_payload.branch || github.head_ref || github.ref_name }}
        fetch-depth: 0
        persist-credentials: false

    - uses: actions/setup-node@v4
      with:
        node-version: 22

    - name: Translate
      env:
        LOCALHERO_API_KEY: ${{ secrets.LOCALHERO_API_KEY }}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GITHUB_BASE_REF: ${{ github.base_ref }}
        LOCALHERO_SYNC_ID: ${{ github.event.client_payload.sync_id }}
        LOCALHERO_SYNC_VERSION: ${{ github.event.client_payload.sync_update_version }}
      run: npx -y @localheroai/cli ci

To generate the action version of this workflow instead, run npx @localheroai/cli init.

Related reading

Need help? Drop us a line at hi@localhero.ai

Last updated

Klar til å prøve?

Kom i gang på noen minutter. Du trenger ikke kredittkort.