Tutorials

GitHub Translation Sync: Automate Your Localization Workflow

Eray Gündoğmuş
Eray Gündoğmuş
·9 min read
Share
GitHub Translation Sync: Automate Your Localization Workflow

Translation files scattered across branches, manual copy-pasting of JSON keys, Slack messages asking "did anyone push the French translations?" — if this sounds familiar, your localization workflow needs automation.

GitHub Translation Sync eliminates the manual overhead of keeping translation files in sync with your codebase. It watches your repository for new translation keys, sends them for translation (AI or human), and delivers completed translations back as pull requests — all without leaving your Git workflow.

This guide walks through setting up GitHub Translation Sync from scratch, configuring PR-based workflows, integrating with CI, and optimizing for team collaboration.


What is GitHub Translation Sync?

GitHub Translation Sync is a bidirectional synchronization mechanism between your Git repository and a translation management platform. Instead of manually pushing and pulling translation files, the sync:

  1. Detects new or changed keys when you push code
  2. Sends them to your translation platform for translation
  3. Delivers completed translations back as pull requests
  4. Keeps translation files in sync across branches

Why PR-Based Translation Delivery?

Traditional localization workflows use CLI push/pull commands. The PR-based approach is better because:

CLI Push/PullPR-Based Sync
Manual process, easy to forgetAutomatic, always in sync
No review step for translationsPR review catches issues
Direct commits to main branchBranch protection respected
No audit trailFull Git history for translations
One person's responsibilityTeam-visible via PR notifications
Merge conflicts are commonSync handles rebasing automatically

Prerequisites

Before setting up GitHub Translation Sync, you need:

  • A GitHub repository with translation files (JSON, YAML, PO, or ARB format)
  • A Better i18n project (sign up at better-i18n.com)
  • Repository admin access (for installing the GitHub App)
  • Translation files in a consistent directory structure

Supported Directory Structures

# Pattern 1: Locale directories
locales/
  en/
    common.json
    dashboard.json
  es/
    common.json
    dashboard.json

# Pattern 2: Locale file suffix
locales/
  common.en.json
  common.es.json
  dashboard.en.json
  dashboard.es.json

# Pattern 3: Single directory with locale prefix
i18n/
  en.json
  es.json
  fr.json

# Pattern 4: Framework-specific (e.g., Flutter ARB)
lib/l10n/
  app_en.arb
  app_es.arb

Step 1: Install the GitHub App

The GitHub Sync works through a GitHub App that has read/write access to your repository's translation files.

  1. Go to your Better i18n project dashboard
  2. Navigate to Settings then Integrations
  3. Click Connect GitHub
  4. Select the repositories you want to sync
  5. Approve the requested permissions

Permissions Explained

PermissionWhy It Is Needed
Read: CodeDetect new/changed translation keys
Read/Write: Pull RequestsCreate PRs with completed translations
Read: MetadataAccess repository information
WebhooksReceive push events for sync triggers

The GitHub App only accesses translation-related files. It does not read your source code, environment variables, or secrets.


Step 2: Configure the Sync

Create a configuration file in your repository root:

# better-i18n.yml
sync:
  # Source language — keys are extracted from this locale's files
  sourceLanguage: en

  # Target languages — translations will be generated for these
  targetLanguages:
    - es
    - fr
    - de
    - ja
    - ko
    - "zh-CN"

  # Path pattern for translation files
  # Supports glob patterns with {locale} and {namespace} placeholders
  paths:
    - "locales/{locale}/{namespace}.json"

  # Alternative: single file per locale
  # paths:
  #   - "locales/{locale}.json"

  # Branch to watch for changes (default: main)
  baseBranch: main

  # How to deliver translations
  delivery:
    # "pr" creates pull requests, "commit" pushes directly (not recommended)
    method: pr

    # PR branch naming pattern
    branchPattern: "i18n/sync-{timestamp}"

    # Auto-merge PRs when all checks pass (requires branch protection)
    autoMerge: false

    # Reviewers to assign to translation PRs
    reviewers:
      - "@i18n-team"

    # Labels to add to translation PRs
    labels:
      - "i18n"
      - "automated"

  # AI translation settings
  ai:
    # Auto-translate new keys with AI
    autoTranslate: true

    # Require human approval before PR is created
    requireApproval: false

    # Custom instructions for AI translator
    instructions: |
      - Use formal register for German (Sie, not du)
      - Keep brand name "Better i18n" untranslated in all languages
      - Use Oxford comma in English translations

Configuration Options Reference

OptionTypeDefaultDescription
sourceLanguagestring"en"Source locale code
targetLanguagesstring[][]Target locale codes
pathsstring[]auto-detectGlob patterns for translation files
baseBranchstring"main"Branch to monitor
delivery.methodstring"pr"Delivery method: pr or commit
delivery.autoMergebooleanfalseAuto-merge when checks pass
delivery.reviewersstring[][]PR reviewers (users or teams)
delivery.labelsstring[]["i18n"]PR labels
ai.autoTranslatebooleantrueAI-translate new keys
ai.requireApprovalbooleanfalseRequire approval before PR
ai.instructionsstring""Custom AI instructions

Step 3: Understand the Sync Workflow

Once configured, here is what happens during a typical development cycle:

Adding New Keys

Developer                 GitHub                Better i18n              GitHub
    |                        |                       |                      |
    |-- push commit -------->|                       |                      |
    |   (adds new key        |-- webhook ----------->|                      |
    |    to en/common.json)  |                       |                      |
    |                        |                       |-- AI translates ---->|
    |                        |                       |   new key for all    |
    |                        |                       |   target languages   |
    |                        |                       |                      |
    |                        |<---------- create PR with translations ------|
    |                        |                       |                      |
    |<-- PR notification ----|                       |                      |
    |                        |                       |                      |
    |-- review & merge ----->|                       |                      |

Updating Existing Keys

When you modify the source text of an existing key:

  1. Sync detects the change via webhook
  2. Existing translations are marked as "needs review"
  3. AI generates updated translations
  4. A PR is created with updated translations
  5. Previous translations are preserved in PR description for reviewer comparison

Deleting Keys

When you remove a key from the source locale:

  1. Sync detects the deletion
  2. The corresponding key is removed from all target locale files
  3. A PR is created with the deletions
  4. PR description lists all removed keys for audit purposes

Step 4: CI Integration

Add translation validation to your CI pipeline to catch issues before translations are merged.

GitHub Actions Workflow

# .github/workflows/i18n-validate.yml
name: Validate Translations

on:
  pull_request:
    paths:
      - "locales/**"
    types: [opened, synchronize]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Bun
        uses: oven-sh/setup-bun@v2

      - name: Install dependencies
        run: bun install

      - name: Validate translation coverage
        run: |
          bunx @better-i18n/cli coverage \
            --min 95 \
            --format github-actions

      - name: Validate ICU syntax
        run: |
          bunx @better-i18n/cli validate \
            --format github-actions

      - name: Check for unused keys
        run: |
          bunx @better-i18n/cli lint \
            --unused \
            --format github-actions

      - name: Comment coverage report on PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const { execSync } = require('child_process');
            const report = execSync('bunx @better-i18n/cli coverage --format markdown').toString();
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## Translation Coverage Report\n\n${report}`
            });

Required Status Checks

Configure branch protection to require translation validation:

  1. Go to Settings then Branches then Branch protection rules
  2. Enable Require status checks to pass before merging
  3. Add these checks:
    • Validate translation coverage
    • Validate ICU syntax

This ensures that no PR can be merged with broken translations or insufficient coverage.


Step 5: Branch Strategy for Translations

The Default: Single Translation Branch

By default, each sync creates a new branch with completed translations:

main ------------------------------------------------
  \                                 |
   \-- i18n/sync-20260315 ---------/
       (translations for new keys)

This works well for most teams. Each sync is isolated, and you can review translations before merging.

Advanced: Feature Branch Sync

For teams using feature branches, configure sync to target feature branches:

# better-i18n.yml
sync:
  baseBranch: main
  featureBranchSync:
    enabled: true
    # Sync translations for PRs targeting these branches
    targetBranches:
      - "main"
      - "develop"
    # Create translation PR targeting the same branch as the feature PR
    followBranch: true

With feature branch sync:

main ------------------------------------------------
  \
   \-- feature/new-checkout -------------------------
        \                           |
         \-- i18n/new-checkout -----/
             (translations for this feature)

Handling Merge Conflicts

The sync handles most merge conflicts automatically by:

  1. Rebasing translation branches before creating PRs
  2. Using JSON-aware merging (not line-based) for translation files
  3. Re-triggering sync if a conflict is detected

If an automatic resolution is not possible, the sync will add a comment to the PR explaining the conflict and suggesting manual resolution steps.


Step 6: Monitoring and Troubleshooting

Sync Status Dashboard

Monitor sync health in your Better i18n dashboard:

  • Sync History: Every sync event with status (success, partial, failed)
  • Pending Translations: Keys waiting for translation
  • Coverage Trends: Translation coverage over time per language
  • PR Activity: Open, merged, and closed translation PRs

Common Issues and Solutions

Issue: Sync not triggering on push

  • Verify the GitHub App is installed on the repository
  • Check that the webhook is active in Settings then Webhooks
  • Ensure the push is to the configured baseBranch
  • Check that changed files match the configured paths patterns

Issue: PR has merge conflicts

  • The sync auto-rebases, but if your branch diverged significantly, manual resolution may be needed
  • Resolve conflicts in the translation PR, then push — the sync will not overwrite your resolution

Issue: AI translations are low quality

  • Add detailed ai.instructions in your config
  • Include glossary terms and brand guidelines
  • Consider enabling requireApproval for critical languages

Issue: Too many small PRs

  • Configure delivery.batchWindow to group changes:
sync:
  delivery:
    # Wait 30 minutes to batch multiple pushes into one PR
    batchWindow: 30

Advanced Configuration

Namespace Filtering

Only sync specific namespaces:

sync:
  paths:
    - "locales/{locale}/common.json"
    - "locales/{locale}/marketing.json"
  # Explicitly exclude internal namespaces
  exclude:
    - "locales/{locale}/internal.json"
    - "locales/{locale}/test-fixtures.json"

Custom Commit Messages

sync:
  delivery:
    commitMessage: "chore(i18n): sync translations for {languages}"
    prTitle: "chore(i18n): translations update ({date})"
    prBody: |
      ## Automated Translation Update

      This PR contains translations synced from Better i18n.

      **Languages updated:** {languages}
      **Keys added:** {keysAdded}
      **Keys updated:** {keysUpdated}
      **Keys removed:** {keysRemoved}

      ---
      _Generated by Better i18n GitHub Sync_

Webhook Events

For custom integrations, you can subscribe to sync events via webhooks:

sync:
  webhooks:
    - url: "https://your-server.com/api/i18n-webhook"
      events:
        - sync.completed
        - sync.failed
        - pr.created
        - pr.merged
      secret: "${WEBHOOK_SECRET}"  # Set in Better i18n dashboard

Real-World Example: E-Commerce App Setup

Here is a complete configuration for a typical e-commerce application with Next.js:

# better-i18n.yml
sync:
  sourceLanguage: en
  targetLanguages:
    - es
    - fr
    - de
    - ja
    - "pt-BR"
    - "zh-CN"

  paths:
    - "messages/{locale}/{namespace}.json"

  baseBranch: main

  delivery:
    method: pr
    branchPattern: "i18n/translations-{date}"
    autoMerge: true  # Auto-merge when CI passes
    reviewers:
      - "@frontend-team"
    labels:
      - "i18n"
      - "auto-merge"
    batchWindow: 15  # Batch changes within 15 minutes

  ai:
    autoTranslate: true
    requireApproval: false
    instructions: |
      E-commerce context:
      - "Cart" should use shopping cart terminology (not vehicle)
      - Currency amounts should not be translated (they are formatted by code)
      - Product names in {curly braces} are variables — do not translate
      - Use formal register for German and Japanese
      - Use informal register for Spanish and Portuguese
      - Keep "Better i18n" as-is in all languages

  featureBranchSync:
    enabled: true
    targetBranches: ["main", "staging"]
    followBranch: true

With this configuration:

  1. Every push to main or staging triggers a sync
  2. New keys are AI-translated within minutes
  3. Translations arrive as auto-mergeable PRs
  4. Feature branches get their own translation PRs
  5. CI validates coverage and syntax before merge

Conclusion

GitHub Translation Sync transforms localization from a manual, error-prone process into an automated pipeline that fits naturally into your existing development workflow. The key principles:

  1. Translations live in Git — full history, branch protection, code review
  2. Sync is automatic — no manual push/pull commands to forget
  3. PRs enable review — translations are reviewed like any other code change
  4. CI enforces quality — coverage thresholds and syntax validation block bad translations
  5. AI accelerates — new keys are translated in minutes, not days

Set up takes about 30 minutes. The time saved in the first week alone typically exceeds the setup investment.


Ready to automate your translation workflow? Get started with Better i18n and connect your GitHub repository in minutes.