Table of Contents
Table of Contents
- Why Automate Translation Sync?
- Architecture Overview
- Step 1: Install the GitHub App
- Step 2: Add Repositories
- Multi-Repository Support
- Step 3: Understand the Webhook Mechanics
- Push Events
- Installation Lifecycle Events
- Step 4: Set Up the PR-Based Translation Workflow
- Why Pull Requests?
- How It Works
- Publishing Translations
- Step 5: Enable the Doctor CI Workflow
- Generating the Workflow
- What Doctor Checks
- Example Doctor Output
- Combining Doctor with the CLI
- Step 6: Configure Pre-Commit Hooks
- Sync Tracking and Observability
- Putting It All Together
- Common Configurations
- Monorepo with Multiple Apps
- Separate Repositories per Platform
- Feature Branch Workflow
- Conclusion
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:
- Go to dash.better-i18n.com and open your project settings
- Click Connect GitHub to install the better-i18n GitHub App
- Select which repositories the app can access — you can grant access to specific repositories or your entire organization
- Authorize the requested permissions
better-i18n requests minimal permissions:
| Permission | Purpose |
|---|---|
| Repository Contents | Read/write translation files (only files matching your configured patterns) |
| Pull Requests | Create PRs for translation updates |
| Webhooks | Receive 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 filessrc/i18n/{locale}.json— Single file per localepackages/*/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:
- GitHub sends the push payload to better-i18n
- The payload signature is verified using HMAC-SHA256
- better-i18n checks if any changed files match your configured file pattern
- If translation files changed, a sync job starts
- New keys are added to the cloud project, changed translations are updated
- 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
| Event | What Happens |
|---|---|
installation.deleted | The integration is automatically uninstalled and all webhook subscriptions are cleaned up |
installation.suspend | Sync pauses — no webhooks are processed until the installation is unsuspended |
installation.unsuspend | Sync 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
- Translators or AI agents update translations in the better-i18n dashboard
- When a batch of translations is ready, trigger a publish
- better-i18n creates a branch in your repository (e.g.,
better-i18n/translations-2026-03-13) - The updated translation JSON files are committed to that branch
- A pull request is opened against your configured base branch
- 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
publishTranslationsfrom 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:
- A developer adds a new feature with user-facing strings
- They use
useTranslations('namespace')orgetTranslations('namespace')in their components - Pre-commit hook runs
scan --stagedand warns about untranslated strings - Developer pushes to GitHub
- The push webhook triggers an inbound sync — new keys appear in the better-i18n dashboard
- Doctor CI runs on the PR and reports translation coverage
- Translators (or AI agents via MCP) translate the new keys
- Translations are published — a PR is created in the repository with updated JSON files
- The team reviews and merges the translation PR
- 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.