Engineering

How to Sync Translations with GitHub Automatically: A Complete CI/CD Localization Guide

Eray Gündoğmuş
Eray Gündoğmuş
·9 min read
Share
How to Sync Translations with GitHub Automatically: A Complete CI/CD Localization Guide

Most teams manage translations through a painful manual loop: export strings, send them to translators, wait, receive files back, copy them into the repository, fix merge conflicts, and repeat. This process breaks down fast when you ship frequently or support multiple languages.

There is a better way. By connecting your GitHub repository directly to your translation management system, you can automate the entire cycle — from detecting new strings in code to delivering finished translations as pull requests.

This guide walks through setting up automatic translation sync with GitHub using better-i18n, covering webhook mechanics, the PR-based delivery workflow, multi-repo configurations, and the Doctor CI health check workflow.

Why Automate Translation Sync?

Before diving into the setup, it is worth understanding what manual translation workflows cost you:

  • Context switching — Developers juggle between code and translation files
  • Stale translations — Manual exports mean translators work on outdated source strings
  • Merge conflicts — JSON translation files are conflict magnets when multiple people touch them
  • Missing coverage — No automated way to know if a new feature shipped without translations
  • Slow delivery — Translations sit in a queue instead of flowing continuously

Automating the sync between GitHub and your translation platform eliminates these bottlenecks. Every push to your repository triggers key detection. Every completed translation ships back as a pull request. CI checks catch missing translations before they reach production.

Architecture Overview

The sync system has two directions:

Inbound (Code to Cloud): When developers push code, GitHub sends a webhook to better-i18n. The platform reads your translation files, detects new or changed keys, and updates the cloud dashboard. Translators see fresh strings immediately.

Outbound (Cloud to Code): When translations are complete, better-i18n creates a pull request in your repository with the updated JSON files. Your team reviews and merges through the standard code review workflow.

Both directions are secured with HMAC-SHA256 signature verification. No unauthenticated payloads are processed.

Step 1: Install the GitHub App

Start by connecting your GitHub account to better-i18n:

  1. Go to dash.better-i18n.com and open your project settings
  2. Click Connect GitHub to install the better-i18n GitHub App
  3. Select which repositories the app can access — you can grant access to specific repositories or your entire organization
  4. Authorize the requested permissions

better-i18n requests minimal permissions:

PermissionPurpose
Repository ContentsRead/write translation files (only files matching your configured patterns)
Pull RequestsCreate PRs for translation updates
WebhooksReceive push events to trigger sync

The app never accesses your source code, environment variables, secrets, or any files outside your translation file patterns.

Step 2: Add Repositories

Once the GitHub App is installed, add the repositories you want to sync. You can do this from the dashboard or programmatically:

github.addRepository({
  project: "your-org/your-project",
  repositoryId: 123456789,
  branch: "main",
  filePattern: "locales/{locale}/{namespace}.json"
})

The filePattern parameter tells better-i18n where to find your translation files. Common patterns include:

  • locales/{locale}/{namespace}.json — Locale folders with namespace files
  • src/i18n/{locale}.json — Single file per locale
  • packages/*/locales/{locale}.json — Monorepo with per-package translations

Multi-Repository Support

You are not limited to a single repository. better-i18n supports several multi-repo configurations:

  • Monorepo: One repository with multiple apps sharing a translation project
  • Micro-frontends: Multiple repositories contributing to a shared translation set
  • Platform + mobile: Web and mobile repositories syncing from the same translation source

Each repository can have its own file pattern and branch configuration. Translations flow between all connected repositories and the central cloud project.

Step 3: Understand the Webhook Mechanics

Once a repository is connected, GitHub sends webhook events to better-i18n. Here is what each event does:

Push Events

The push event is the primary sync trigger. When you push code to a connected branch:

  1. GitHub sends the push payload to better-i18n
  2. The payload signature is verified using HMAC-SHA256
  3. better-i18n checks if any changed files match your configured file pattern
  4. If translation files changed, a sync job starts
  5. New keys are added to the cloud project, changed translations are updated
  6. The sync status is tracked and visible in the dashboard

Only pushes to configured branches trigger sync. Pushing to a feature branch will not trigger sync unless you have explicitly configured it.

Installation Lifecycle Events

EventWhat Happens
installation.deletedThe integration is automatically uninstalled and all webhook subscriptions are cleaned up
installation.suspendSync pauses — no webhooks are processed until the installation is unsuspended
installation.unsuspendSync resumes — webhooks are processed again normally

These events ensure the integration stays in sync with your GitHub App installation state. If an organization admin suspends the app, better-i18n stops processing immediately instead of accumulating failed webhook deliveries.

Step 4: Set Up the PR-Based Translation Workflow

The outbound flow — delivering translations back to your repository — uses pull requests rather than direct pushes. This is a deliberate design choice:

Why Pull Requests?

  • Code review applies to translations too. Reviewers can spot context issues, placeholder errors, or formatting problems before translations reach production.
  • CI runs against translation changes. Your test suite, linters, and build process validate that new translations do not break anything.
  • Full audit trail in git. Every translation change is a commit with a clear diff showing exactly what changed.
  • Easy rollback. Revert a translation PR the same way you revert any other change.

How It Works

  1. Translators or AI agents update translations in the better-i18n dashboard
  2. When a batch of translations is ready, trigger a publish
  3. better-i18n creates a branch in your repository (e.g., better-i18n/translations-2026-03-13)
  4. The updated translation JSON files are committed to that branch
  5. A pull request is opened against your configured base branch
  6. Your team reviews the diff and merges when satisfied

The PR includes only translation files — no other files are touched. The diff clearly shows which keys changed and in which languages.

Publishing Translations

You can trigger a publish through multiple channels:

  • Dashboard: Click the "Publish to GitHub" button in the project settings
  • REST API: Call the publish endpoint programmatically
  • MCP tools: Use publishTranslations from AI agent workflows

Step 5: Enable the Doctor CI Workflow

The Doctor workflow is a GitHub Actions workflow that monitors translation health on every commit. Think of it as ESLint for your translations.

Generating the Workflow

Use the tRPC API to generate the workflow file:

github.createDoctorWorkflow({
  project: "your-org/your-project",
  repositoryId: 123456789
})

This creates a .github/workflows/i18n-doctor.yml file in your repository.

What Doctor Checks

Missing translations — Keys present in your source language but absent in one or more target languages. Doctor reports exact counts per language.

Unused keys — Keys defined in your translation files but never referenced in your codebase. These add bloat to your translation bundles and confuse translators.

Format consistency — ICU message syntax validation across all languages. Catches errors like mismatched placeholders ({count} in English but {nombre} in Spanish) or broken plural rules.

Coverage report — A per-language completion percentage so you can track progress at a glance.

Example Doctor Output

i18n Doctor Report
==================

Coverage:
  en: 100% (source)
  tr: 94.2% (missing 23 keys)
  de: 87.1% (missing 51 keys)
  ja: 78.3% (missing 86 keys)

Unused keys: 12
Format errors: 0

Result: WARNING — 3 languages below 95% threshold

Doctor integrates with GitHub's check system. Pull requests that introduce missing translations or format errors display a warning status, giving reviewers immediate visibility into translation impact.

Combining Doctor with the CLI

For the most thorough coverage, combine Doctor with the better-i18n CLI in your CI pipeline:

name: i18n Health Check
on: [push, pull_request]

jobs:
  i18n:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"

      # Scan for hardcoded strings that should be translated
      - name: Scan for untranslated strings
        run: npx @better-i18n/cli scan --ci

      # Compare local keys with cloud project
      - name: Sync coverage report
        run: npx @better-i18n/cli sync --format json

      # Doctor runs automatically via the generated workflow

The scan --ci command exits with a non-zero code if untranslated strings are found, blocking the PR. The sync --format json command outputs a machine-readable coverage report.

Step 6: Configure Pre-Commit Hooks

Catch translation issues even earlier — before code is committed — with pre-commit hooks:

# Using Husky
npx husky init
echo "npx @better-i18n/cli scan --staged --ci" > .husky/pre-commit

The --staged flag tells the CLI to only scan files in the current git staging area, keeping the hook fast even in large codebases.

For more granular control, use lint-staged:

{
  "lint-staged": {
    "*.{tsx,jsx}": ["better-i18n scan --ci"]
  }
}

Sync Tracking and Observability

Every sync operation — inbound or outbound — is tracked as a job with full status and logs. You can query sync history through the MCP tools or REST API:

Sync types you will see:

  • initial_import — The first sync when a repository is connected. Imports all existing translation files.
  • source_sync — Triggered by a GitHub push event. Syncs changed translation files to the cloud.
  • cdn_upload — Deploys translations to the CDN for runtime consumption.
  • batch_publish — Publishes translations back to GitHub as a pull request.

Each job includes timestamps, status (pending, running, completed, failed), and detailed logs for debugging.

Putting It All Together

Here is the complete automated translation workflow after setup:

  1. A developer adds a new feature with user-facing strings
  2. They use useTranslations('namespace') or getTranslations('namespace') in their components
  3. Pre-commit hook runs scan --staged and warns about untranslated strings
  4. Developer pushes to GitHub
  5. The push webhook triggers an inbound sync — new keys appear in the better-i18n dashboard
  6. Doctor CI runs on the PR and reports translation coverage
  7. Translators (or AI agents via MCP) translate the new keys
  8. Translations are published — a PR is created in the repository with updated JSON files
  9. The team reviews and merges the translation PR
  10. CDN deployment makes translations available at runtime immediately

The entire cycle runs without manual file copying, export/import steps, or merge conflict resolution. Translations flow continuously between your codebase and the cloud.

Common Configurations

Monorepo with Multiple Apps

your-monorepo/
├── apps/
│   ├── web/
│   │   └── locales/{locale}/{namespace}.json
│   └── admin/
│       └── locales/{locale}/{namespace}.json

Connect the repository once, with a file pattern that covers both apps: apps/*/locales/{locale}/{namespace}.json.

Separate Repositories per Platform

Connect each repository independently with its own file pattern. Translations are shared through the central cloud project — update once, publish to all repositories.

Feature Branch Workflow

By default, sync triggers only on pushes to your configured base branch (usually main). For teams that want translation sync on feature branches, configure multiple branches in the repository settings.

Conclusion

Automating translation sync with GitHub eliminates the manual bottlenecks that slow down international product development. Webhook-driven inbound sync keeps translators working on fresh strings. PR-based outbound delivery ensures translation changes go through the same review process as code. Doctor CI catches coverage gaps before they reach production.

The setup takes about fifteen minutes: install the GitHub App, add your repositories, configure file patterns, and enable the Doctor workflow. From that point on, translations flow automatically between your codebase and the cloud.

If you are managing translations across multiple repositories or shipping to users in more than a few languages, this automation pays for itself on the first sprint.