IntlPull
Guide
11 min read

Git-Based Translation Workflow: Managing Translations in Your Repository

Complete guide to managing translations using git workflows. Learn file-in-repo patterns, PR-based review, CI/CD integration, and automated validation for i18n files.

IntlPull Team
IntlPull Team
Feb 12, 2026
On this page
Summary

Complete guide to managing translations using git workflows. Learn file-in-repo patterns, PR-based review, CI/CD integration, and automated validation for i18n files.

What is a Git-Based Translation Workflow?

A git-based translation workflow treats translation files as first-class code artifacts stored directly in your version control repository. Translation keys, values, and locale files live alongside source code, following the same development lifecycle: branching, pull requests, code review, CI/CD validation, and deployment. This approach creates a single source of truth where translations are versioned, reviewed, and deployed using the same infrastructure and processes as application code.

Unlike external TMS platforms where translations live in separate databases, git-based workflows keep everything in-repo. Developers commit new translation keys, translators submit changes via pull requests, automated tests validate formatting and completeness, and deployments include the latest translations automatically. This tight integration appeals to engineering teams who value consistency, auditability, and developer-friendly tooling.

Git-Based vs TMS-Based Workflows

Git-Based Workflow Characteristics

Single Source of Truth: Translation files reside in the repository. The deployed application always reflects the git state. No synchronization delays or drift between external systems and production code.

Developer-Centric: Engineers work with familiar git commands, text editors, and PR workflows. No context switching to separate translation platforms or learning new UIs.

Version History: Complete translation change history with git blame, file diffs, and commit messages. Easy rollback to previous translations if issues occur.

CI/CD Integration: Translation validation runs alongside unit tests and linting. Automated checks prevent malformed i18n files from merging.

Challenges:

  • Translators need git/GitHub knowledge or wrapper tools
  • Merge conflicts in translation files
  • No built-in translation memory or terminology management
  • Manual coordination for professional translation services
  • Difficult to track translation status across languages

TMS-Based Workflow Characteristics

Translator-Friendly: Web interfaces designed for translators, not developers. In-context editing, translation memory, glossaries, and collaboration features.

Translation Memory: Automatic reuse of previous translations across keys and projects. Reduces translation time and cost.

Professional Translator Integration: Export/import workflows for professional translation agencies. Supports standard formats (XLIFF, TMX) that translators expect.

Status Tracking: Visual dashboards showing translation progress per language, missing keys, and quality metrics.

Challenges:

  • Synchronization overhead between TMS and repository
  • Potential for drift if sync fails or manual edits occur
  • Additional tool in the stack to learn and maintain
  • Data lives outside version control
  • Integration complexity with CI/CD pipelines

Hybrid Approaches

Many teams combine both: TMS with git sync. Translators work in a user-friendly TMS interface, while automated sync keeps the repository updated. IntlPull's GitHub integration exemplifies this: translators use the web platform for context and collaboration, while the TMS automatically commits changes to your repository via pull requests or direct commits.

File-in-Repo Pattern Architecture

Directory Structure Best Practices

Option 1: Flat Structure (Simple Projects)

/locales
  ├── en.json
  ├── es.json
  ├── fr.json
  └── de.json

Best for: Small applications with few translation keys (< 500 per language).

Option 2: Nested by Language (Recommended)

/locales
  ├── en/
  │   ├── common.json
  │   ├── auth.json
  │   ├── dashboard.json
  │   └── errors.json
  ├── es/
  │   ├── common.json
  │   ├── auth.json
  │   └── ...
  └── fr/
      └── ...

Best for: Medium to large applications. Namespaces reduce merge conflicts and improve organization.

Option 3: Nested by Namespace (Alternative)

/locales
  ├── common/
  │   ├── en.json
  │   ├── es.json
  │   └── fr.json
  ├── auth/
  │   ├── en.json
  │   └── ...
  └── dashboard/
      └── ...

Best for: Teams where developers work on specific features. Easier to locate all translations for a namespace.

Option 4: Platform-Specific (Multi-Platform Projects)

/apps
  ├── web/locales/
  │   ├── en.json
  │   └── es.json
  ├── mobile/android/values/
  │   ├── strings.xml
  │   └── strings-es.xml
  └── mobile/ios/Localizable/
      ├── en.lproj/Localizable.strings
      └── es.lproj/Localizable.strings

Best for: Monorepos with web, iOS, and Android apps. Each platform uses native formats.

File Format Selection

JSON (Recommended for Web)

JSON
1{
2  "common": {
3    "buttons": {
4      "submit": "Submit",
5      "cancel": "Cancel"
6    }
7  },
8  "auth": {
9    "login": {
10      "title": "Sign in to your account",
11      "email": "Email address",
12      "password": "Password"
13    }
14  }
15}

Pros: Human-readable, nested structure, widely supported, easy git diffs. Cons: No pluralization metadata (requires i18n library support), no comments.

YAML (Alternative for Web)

YAML
1common:
2  buttons:
3    submit: Submit
4    cancel: Cancel
5auth:
6  login:
7    title: Sign in to your account
8    email: Email address

Pros: More readable than JSON, supports comments, less syntax noise. Cons: Indentation-sensitive (merge conflict challenges), slower parsing than JSON.

PO/POT Files (Gettext Standard)

PO
1msgid "auth.login.title"
2msgstr "Sign in to your account"
3
4msgid "items"
5msgid_plural "items"
6msgstr[0] "item"
7msgstr[1] "items"

Pros: Industry standard, supports pluralization/context, translator-friendly. Cons: Less intuitive for developers, requires build step.

Platform-Native Formats

  • iOS: .strings or .stringsdict files
  • Android: strings.xml with resource qualifiers
  • React Native: Typically JSON, sometimes platform-native per platform

Choose based on your i18n library and team preferences. JSON strikes the best balance for most web projects.

PR-Based Translation Review

Translation Change PR Workflow

1. Developer Creates Translation Keys When adding new features, developers create translation keys in the source language (usually English) and include them in the feature branch.

Terminal
1# Developer adds new feature with translations
2git checkout -b feature/user-settings
3
4# Add new keys to en.json
5{
6  "settings": {
7    "title": "Account Settings",
8    "privacy": {
9      "title": "Privacy Settings",
10      "description": "Manage your privacy preferences"
11    }
12  }
13}
14
15git add locales/en.json
16git commit -m "feat: add user settings page with i18n"
17git push origin feature/user-settings

2. PR Review Includes Translation Keys Code reviewers verify that:

  • New features have translation keys (no hardcoded strings)
  • Key names follow naming conventions
  • Source language translations are clear and complete
  • Keys are organized in appropriate namespaces

3. Translation Keys Merge to Main After PR approval, feature merges with English translations. Other languages now have missing keys.

4. Translators Submit Translation PRs Translators (internal or external) create PRs adding translations for missing keys.

Terminal
1# Translator creates PR for Spanish translations
2git checkout -b translations/es-user-settings
3
4# Add Spanish translations
5{
6  "settings": {
7    "title": "Configuración de cuenta",
8    "privacy": {
9      "title": "Configuración de privacidad",
10      "description": "Administra tus preferencias de privacidad"
11    }
12  }
13}
14
15git add locales/es.json
16git commit -m "i18n: add Spanish translations for user settings"
17git push origin translations/es-user-settings

5. Translation Review Native speakers or translation leads review PRs for:

  • Linguistic accuracy
  • Cultural appropriateness
  • Consistency with existing terminology
  • Proper formatting (placeholders, punctuation)

6. Automated Validation CI checks validate:

  • JSON/YAML syntax is valid
  • Placeholder variables match source language
  • No duplicate keys
  • Character limits respected (if configured)

Review Guidelines and Checklist

For Code Reviewers:

  • No hardcoded user-facing strings in code
  • Translation keys follow naming convention (namespace.component.element)
  • Keys include context comments for translators
  • Placeholders use consistent format ({variable} or {{variable}})
  • Pluralization handled correctly with library-specific syntax

For Translation Reviewers:

  • Translation accurately conveys source meaning
  • Terminology matches glossary and existing translations
  • Tone appropriate for brand voice
  • Formatting preserved (bold, italics, links)
  • Placeholder positions make sense in target language
  • No truncation or overflow issues with UI constraints

Git Hooks for i18n Validation

Pre-Commit Hook: Validate Translation Files

Prevent committing malformed translation files before they reach the repository.

Using Husky + lint-staged:

JSON
1// package.json
2{
3  "lint-staged": {
4    "locales/**/*.json": [
5      "npm run i18n:validate",
6      "npm run i18n:format"
7    ]
8  }
9}

Validation Script:

JavaScript
1// scripts/validate-translations.js
2const fs = require('fs');
3const path = require('path');
4
5const validateTranslationFile = (filePath) => {
6  try {
7    const content = fs.readFileSync(filePath, 'utf8');
8    const json = JSON.parse(content);
9
10    // Check for duplicate keys
11    const keys = new Set();
12    const findDuplicates = (obj, prefix = '') => {
13      Object.keys(obj).forEach(key => {
14        const fullKey = prefix ? `${prefix}.${key}` : key;
15        if (typeof obj[key] === 'object') {
16          findDuplicates(obj[key], fullKey);
17        } else {
18          if (keys.has(fullKey)) {
19            throw new Error(`Duplicate key: ${fullKey}`);
20          }
21          keys.add(fullKey);
22        }
23      });
24    };
25
26    findDuplicates(json);
27
28    console.log(`${filePath} is valid`);
29    return true;
30  } catch (error) {
31    console.error(`${filePath}: ${error.message}`);
32    process.exit(1);
33  }
34};
35
36// Validate all translation files
37const localesDir = path.join(__dirname, '../locales');
38// Recursively validate all JSON files

Pre-Push Hook: Check for Missing Translations

Warn developers before pushing if translation keys are missing in other languages.

JavaScript
1// scripts/check-missing-keys.js
2const fs = require('fs');
3const path = require('path');
4
5const loadKeys = (obj, prefix = '') => {
6  let keys = [];
7  Object.keys(obj).forEach(key => {
8    const fullKey = prefix ? `${prefix}.${key}` : key;
9    if (typeof obj[key] === 'object' && obj[key] !== null) {
10      keys = keys.concat(loadKeys(obj[key], fullKey));
11    } else {
12      keys.push(fullKey);
13    }
14  });
15  return keys;
16};
17
18const sourceFile = './locales/en.json';
19const sourceKeys = loadKeys(JSON.parse(fs.readFileSync(sourceFile)));
20
21const languages = ['es', 'fr', 'de'];
22languages.forEach(lang => {
23  const targetFile = `./locales/${lang}.json`;
24  const targetKeys = loadKeys(JSON.parse(fs.readFileSync(targetFile)));
25
26  const missing = sourceKeys.filter(key => !targetKeys.includes(key));
27
28  if (missing.length > 0) {
29    console.warn(`${lang}.json is missing ${missing.length} keys:`);
30    missing.slice(0, 10).forEach(key => console.warn(`  - ${key}`));
31    if (missing.length > 10) {
32      console.warn(`  ... and ${missing.length - 10} more`);
33    }
34  }
35});

Configure as warning (non-blocking) in pre-push hook to inform but not prevent pushes.

CI/CD Pipeline Integration

GitHub Actions Workflow

Complete i18n Validation Pipeline:

YAML
1name: i18n Validation
2
3on:
4  pull_request:
5    paths:
6      - 'locales/**'
7  push:
8    branches:
9      - main
10    paths:
11      - 'locales/**'
12
13jobs:
14  validate:
15    runs-on: ubuntu-latest
16    steps:
17      - uses: actions/checkout@v3
18
19      - name: Setup Node.js
20        uses: actions/setup-node@v3
21        with:
22          node-version: '18'
23
24      - name: Install dependencies
25        run: npm ci
26
27      - name: Validate JSON syntax
28        run: npm run i18n:validate
29
30      - name: Check for missing keys
31        run: npm run i18n:check-missing
32
33      - name: Check placeholder consistency
34        run: npm run i18n:check-placeholders
35
36      - name: Run i18n tests
37        run: npm run test:i18n
38
39      - name: Comment missing keys on PR
40        if: github.event_name == 'pull_request'
41        uses: actions/github-script@v6
42        with:
43          script: |
44            const fs = require('fs');
45            const report = fs.readFileSync('./i18n-report.json', 'utf8');
46            const data = JSON.parse(report);
47
48            if (data.missingKeys.length > 0) {
49              github.rest.issues.createComment({
50                issue_number: context.issue.number,
51                owner: context.repo.owner,
52                repo: context.repo.repo,
53                body: `## Missing Translation Keys\n\n${data.missingKeys.join('\n')}`
54              });
55            }

Placeholder Consistency Validation

Ensure variables in translations match the source language.

JavaScript
1// scripts/check-placeholders.js
2const extractPlaceholders = (text) => {
3  const regex = /{(w+)}/g;
4  const matches = [];
5  let match;
6  while ((match = regex.exec(text)) !== null) {
7    matches.push(match[1]);
8  }
9  return matches.sort();
10};
11
12const validatePlaceholders = (sourceText, targetText, key) => {
13  const sourcePlaceholders = extractPlaceholders(sourceText);
14  const targetPlaceholders = extractPlaceholders(targetText);
15
16  const sourceStr = sourcePlaceholders.join(',');
17  const targetStr = targetPlaceholders.join(',');
18
19  if (sourceStr !== targetStr) {
20    throw new Error(
21      `Key "${key}": placeholder mismatch\n` +
22      `  Source: ${sourceStr}\n` +
23      `  Target: ${targetStr}`
24    );
25  }
26};
27
28// Iterate all keys and validate

Automated Translation Deployment

Deploy translations automatically on merge to main:

YAML
1name: Deploy Translations
2
3on:
4  push:
5    branches:
6      - main
7    paths:
8      - 'locales/**'
9
10jobs:
11  deploy:
12    runs-on: ubuntu-latest
13    steps:
14      - uses: actions/checkout@v3
15
16      - name: Build application
17        run: npm run build
18
19      - name: Deploy to production
20        run: npm run deploy
21        env:
22          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
23
24      - name: Invalidate CDN cache
25        run: |
26          # Clear CDN cache for translation files
27          npm run cdn:invalidate -- /locales/*

Conflict Resolution Strategies

Common Merge Conflict Scenarios

Scenario 1: Two PRs Add Different Keys PR A adds settings.theme, PR B adds settings.language. Both merge cleanly as they modify different parts of the JSON tree.

Scenario 2: Same Key, Different Values Two translators submit different translations for the same key in the same language.

DIFF
1<<<<<<< HEAD
2  "welcome": "Bienvenido a nuestra aplicación"
3=======
4  "welcome": "Bienvenido a nuestro app"
5>>>>>>> translations/es-welcome

Resolution: Translation lead chooses the better version or combines elements from both.

Scenario 3: Reordering Keys One PR alphabetizes keys, another adds new keys in original order.

Prevention: Use automated formatters (prettier) to enforce consistent key ordering. Configure in pre-commit hook.

Conflict Prevention Tactics

1. Namespace Isolation Organize translations by feature/namespace. Different features rarely conflict.

/locales/en/
  ├── auth.json       # Authentication team
  ├── dashboard.json  # Dashboard team
  ├── settings.json   # Settings team

2. Translation Rotation Assign languages to specific translators to avoid simultaneous edits.

3. Automated Formatting Use prettier or custom formatters to ensure consistent key ordering and formatting.

JSON
1// .prettierrc
2{
3  "tabWidth": 2,
4  "useTabs": false,
5  "trailingComma": "none",
6  "overrides": [
7    {
8      "files": "locales/**/*.json",
9      "options": {
10        "printWidth": 120
11      }
12    }
13  ]
14}

4. Small, Focused PRs Encourage translators to submit smaller PRs for specific namespaces rather than large PRs touching many files.

5. Rebase Before Merge Translators rebase their branches on latest main before creating PR to catch conflicts early.

Terminal
1git checkout translations/es-dashboard
2git fetch origin
3git rebase origin/main
4# Resolve conflicts locally before pushing
5git push --force-with-lease

Branching Strategies for Translations

Strategy 1: Feature Branch Includes Translations

Developers add source language translations in the same PR as the feature code.

Workflow:

  1. Feature branch includes code + English translations
  2. PR reviewed and merged
  3. Separate PRs for other languages follow

Pros:

  • Features never deploy without at least source language strings
  • Clean separation between development and translation
  • Easy to track what needs translation

Cons:

  • Other languages lag behind
  • Need fallback strategy for missing translations

Strategy 2: Translation Branches Per Language

Long-running branches for each language that periodically sync with main.

Workflow:

  1. Main branch has English only
  2. translations/es, translations/fr, etc. branches maintained by translators
  3. Translators regularly merge main into their branch
  4. Completed translations merge back to main

Pros:

  • Translators have dedicated workspace
  • Can batch multiple translations before merging

Cons:

  • Merge conflicts more likely with long-running branches
  • Complex to manage multiple active branches
  • Risk of translation branches drifting from main

Strategy 3: Trunk-Based with Translation Tasks

All work happens on short-lived branches that merge to main quickly.

Workflow:

  1. Developer merges feature with source language
  2. Automated system creates GitHub issues for missing translations
  3. Translators claim issues and submit PRs
  4. PRs merge to main when complete

Pros:

  • Simple branching model
  • Clear tracking of translation status via issues
  • Fast integration

Cons:

  • Requires discipline to avoid incomplete translations in main
  • Need good issue tracking system

Recommended: Strategy 1 (feature branch includes source language) combined with Strategy 3 (trunk-based with task tracking) works best for most teams.

IntlPull's GitHub Integration

While git-based workflows offer developer-friendly version control, they burden translators with technical complexity. IntlPull bridges this gap with bidirectional GitHub synchronization.

How It Works

1. Connect Repository: Link your GitHub repository to IntlPull with one click. Select the branch and file path pattern.

2. Automatic Sync: IntlPull monitors your repository for new translation keys. When developers push changes, IntlPull imports new keys automatically.

3. Translator-Friendly Interface: Translators work in IntlPull's web UI with context, screenshots, translation memory, and glossaries—no git knowledge required.

4. Automated Commits: When translators complete translations, IntlPull commits changes back to your repository via pull requests or direct commits (configurable).

5. CI/CD Integration: IntlPull validates translations before committing. Your CI pipeline treats IntlPull commits like any other PR with full validation.

Benefits Over Pure Git Workflow

No Technical Barrier for Translators: Translators use a web interface designed for localization, not a developer terminal.

Translation Memory Across Projects: Reuse translations from previous work automatically—not possible with git-only workflows.

In-Context Editing: Translators see screenshots and context for each key, reducing errors and questions.

Professional Translator Support: Export to XLIFF for external agencies, import results—standard workflow for professional translation.

Conflict-Free Collaboration: Multiple translators work simultaneously without git merge conflicts. IntlPull handles concurrency.

Developer Workflow Unchanged: Developers continue using git as normal. IntlPull operates invisibly in the background.

This hybrid approach gives developers the version control they need while providing translators with proper localization tools.

Frequently Asked Questions

How do I handle translations for multiple platforms in a monorepo?

Use separate locale directories per platform with appropriate formats: JSON for web in apps/web/locales, strings.xml for Android in apps/android/app/src/main/res/values, and .strings for iOS in apps/ios/Resources. Share a source of truth (usually JSON) and use build scripts to convert to platform-native formats during compilation. Tools like i18n-extract can generate Android/iOS files from JSON.

Should translation files be committed directly to main or via PR?

Use PRs for translation changes to enable review and validation. Configure CI to run syntax checks and placeholder validation. Require at least one approval from a native speaker or translation lead. Direct commits to main risk broken translations reaching production. For urgent fixes, create PR and merge after CI passes.

How do I prevent developers from hardcoding strings instead of using i18n keys?

Add ESLint rules to detect hardcoded strings in JSX/templates. For React: use jsx-no-literals rule. For Vue: vue/no-bare-strings-in-template. Configure exceptions for non-translatable content (developer console messages, technical identifiers). Enforce in CI pipeline so PRs with hardcoded strings cannot merge.

What's the best way to handle translation keys that are removed?

Before deleting keys from source language, check usage across codebase with grep/search. Comment out keys for one release cycle rather than deleting immediately in case usage was missed. Use automated tools like i18n-unused to identify truly unused keys. When deleting, create PR that removes key from all language files simultaneously to maintain consistency.

How can I track translation progress across languages in a git-based workflow?

Create a script that compares keys between source language and target languages, outputting a report of missing translations per language. Run this in CI and generate a badge or dashboard. Example: npm run i18n:status outputs en: 100%, es: 87%, fr: 92%. Store results as JSON and visualize with a simple HTML report committed to the repo or published to GitHub Pages.

Should I use git submodules for shared translations across multiple repositories?

Avoid git submodules for translations due to complexity and synchronization challenges. Instead, publish translations as an npm package or use a monorepo structure. If repositories must be separate, use automated scripts to copy translation files between repos during build. IntlPull can sync translations to multiple repositories from a single project, avoiding submodule complexity entirely.

How do I handle emergency translation fixes in production?

Create a hotfix branch from production tag, update translation files, run validation tests, and deploy via accelerated review process. After production fix, cherry-pick changes to main branch to keep history synchronized. For critical issues, consider feature flags to disable broken translations until fix deploys. IntlPull's OTA updates allow pushing translation fixes without app redeployment, bypassing the emergency release process entirely.

Tags
git
workflow
translations
ci-cd
automation
github
version-control
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.