Lingo.dev runs your localization automatically in CI/CD. There are two ways to do it: the GitHub App, which reacts to your pushes and pull requests server-side, or the CLI run inside your own pipeline. Both keep translations in sync on every change, and both process only the strings that actually changed - so localization stays fast and cost-efficient as your project grows.
On GitHub? Start with the App
The GitHub App is the recommended path on GitHub. Install it once, and it localizes on every push and pull request with no runner, no CI secret, and no lockfile to manage. If you're not on GitHub, or you want translation to run alongside your other CI steps, run the CLI in your own pipeline instead.
Option 1: GitHub App (recommended)#
The GitHub App is the simplest way to run continuous localization. It watches your repository and translates server-side, so there's nothing to install in your pipeline and no API key to store as a CI secret.
Setup is a one-time thing:
- Install the Lingo.dev GitHub App on your repository.
- Commit a
.lingo/config.jsonwith yourorgId,engineId, source locale, target locales, and the files to translate. Runlingo initandlingo linklocally to generate it, then commit the result. - Push.
From then on the App handles both workflow styles for you:
- On a push to your default branch, it commits translations back to the branch.
- On a pull request, it adds the translations to that PR so you can review them before merging.
Because the App tracks translation state server-side, there's no lockfile in your repo to conflict on, and no verification step to wire up - it simply keeps your target locales in sync with your source.
Option 2: CLI in your own pipeline#
When you're not on GitHub, or you want localization to run as an explicit step next to your build and test jobs, run the CLI yourself. It works in any CI environment with Node.js 22 or newer - GitHub Actions, GitLab CI, Bitbucket Pipelines, or anything else.
The flow is always the same:
- Install
@lingo.dev/cli. - Authenticate with your API key via the
LINGO_API_KEYenvironment variable. - Run
lingo pushto translate changed strings. - Commit the results, or open a pull request from your job.
Store your Lingo.dev API key as a CI secret and expose it as LINGO_API_KEY. The committed .lingo/config.json tells the CLI what to translate, and the committed .lingo/lock.json (regenerated by lingo push) tracks state so only changed strings are processed.
Minimal GitHub Actions example#
This workflow installs the CLI, translates on every push to main, and commits the results back:
name: Localize
on:
push:
branches: [main]
permissions:
contents: write
jobs:
localize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm install -g @lingo.dev/cli
- run: lingo push
env:
LINGO_API_KEY: ${{ secrets.LINGO_API_KEY }}
- name: Commit translations
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "chore: update translations" || echo "No changes"
git pushThe same two lines - npm install -g @lingo.dev/cli then lingo push - drop into a GitLab CI script: block or a Bitbucket Pipelines step. Set LINGO_API_KEY as a masked/protected CI variable in those platforms.
First run and new locales
The first time you run in CI, or whenever you add a target locale, use lingo push --backfill-missing so every existing string gets translated into the new locale. After that, a plain lingo push translates only the delta.
Commit or pull request?#
Whichever path you pick, you still choose how translations land:
| Approach | How it works | Best for | Trade-off |
|---|---|---|---|
| Commit directly | Translations are committed to the branch that changed | Small teams, zero friction | No review step for translations |
| Pull request | Translations land in a PR for review before merge | Teams that review translations | Requires PR approval |
The GitHub App gives you both automatically - it commits on pushes to your default branch and adds translations to open PRs. In your own pipeline, the choice is yours: commit the results as shown above, or have the job open a pull request instead of pushing to the branch.
Verifying translations before deploy#
To make sure no untranslated content ships, add lingo check as a gate before your deploy step. It exits with a non-zero status when any strings still need translation:
- name: Verify translations
run: lingo checkThe GitHub App keeps locales in sync continuously, so a dedicated verification step is mainly useful when you run the CLI yourself.
Monorepos#
For monorepos where each package has its own .lingo/config.json, scope the run to a package by passing a glob, or run the CLI from that package's directory:
lingo push "apps/web/**"