CI/CD for Localization: Repo-Connected API Pipelines
Building CI/CD pipelines for localization: repo-connected API workflows that extract, translate, validate, and merge strings automatically, eliminating the manual handoffs that cause drift, missed strings, and broken releases.

Shipping multilingual software should not depend on a manual handoff between engineering and localization teams. Yet in most organizations, that is exactly what happens: developers extract strings, email files to translators, wait days for delivery, and manually commit the results, introducing drift, missed strings, and broken releases. A CI/CD pipeline connected to translation APIs eliminates this gap by treating localization as code: string changes are detected on push, translation jobs are dispatched automatically, quality gates block merges when coverage is incomplete, and compiled resource bundles are generated as build artifacts. This article provides a reference architecture for wiring your GitHub or GitLab repository to translation APIs, with concrete YAML snippets, retry strategies, environment separation, and rollback patterns. If your team ships software, documentation, or video content across languages, this is the infrastructure blueprint you need.
If you're already feeling the pain of manual localization handoffs, see how Ollang automates these workflows end to end: Get a walkthrough.
Why CI/CD Pipelines Need a Localization Stage
Modern CI/CD pipelines handle linting, testing, security scanning, and deployment, but localization is almost always bolted on as an afterthought. This creates three systemic problems.
First, string drift. When translations lag behind source code by even a single sprint, users see untranslated keys, stale copy, or outright broken UI in non-primary locales. According to the 2023 Nimdzi 100 report, the language services industry surpassed $60 billion in revenue, yet the majority of engineering teams still manage translations through file uploads and spreadsheets rather than automated pipelines.
Second, release blocking without visibility. Without a localization quality gate, teams either ship incomplete translations or delay the entire release while someone manually checks coverage. Neither outcome is acceptable.
Third, multi-asset blindness. Software strings are only one content type. Product documentation, subtitle files, and legal text all live in or near the same repositories. A CI/CD localization stage that only handles .json or .xml resource files leaves the rest unmanaged.
A properly integrated localization stage treats translated content the same way a test suite treats code correctness: it runs automatically, reports status, and blocks promotion when the output is not ready.
Reference Architecture: Repo to Translation API
The architecture is straightforward in concept: a commit triggers a pipeline job that detects changed source strings, sends them to a translation API, polls or receives a webhook for completion, validates the result, and commits the translated files back to the repository. In practice, the details matter.
Detecting String Diffs on Push
The pipeline should not re-submit every string on every commit. Instead, it compares the current source file against the last known translated snapshot. A simple approach uses git diff scoped to your resource file paths:
# GitHub Actions step: detect changed resource files
- name: Detect changed strings
id: diff
run: |
CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} -- 'src/locales/en/**')
if [ -z "$CHANGED" ]; then
echo "no_changes=true" >> $GITHUB_OUTPUT
else
echo "no_changes=false" >> $GITHUB_OUTPUT
echo "$CHANGED" > changed_files.txt
fi
A more precise approach parses the resource files and computes a key-level diff. For JSON resource bundles, a small script can compare keys and values between the previous and current versions, emitting only the delta. This keeps API calls minimal and avoids redundant translation charges.
Creating and Updating Keys via TMS or Direct API
Once you have the diff, the next step is to submit it. There are two broad patterns:
- TMS API integration: Upload changed files or key-value pairs to a translation management system (such as Phrase, Lokalise, or Crowdin) that handles job routing, translator assignment, and translation memory internally.
- Direct provider API: Call a machine translation API (DeepL, Google Cloud Translation, or similar) directly, handling glossary application and post-editing workflow yourself.
A typical TMS upload call looks like this:
curl -X POST "https://api.example-tms.com/v2/projects/${PROJECT_ID}/files" \
-H "Authorization: Bearer ${TMS_API_TOKEN}" \
-F "file=@src/locales/en/messages.json" \
-F "update_strategy=merge" \
-F "target_locales=fr,de,ja,es"
The update_strategy=merge parameter is critical: it tells the TMS to add new keys and update changed values without deleting keys that still exist in the source but were not part of this diff. This is an idempotency safeguard, re-running the same pipeline step produces the same result.
Kicking Off Translation Jobs
Submitting files and starting a translation job are often separate API calls. After uploading, you create a job or order:
{
"source_locale": "en",
"target_locales": ["fr", "de", "ja"],
"file_ids": ["file_abc123"],
"workflow": "machine_translate_then_review",
"callback_url": "https://ci.example.com/hooks/translation-complete"
}
The callback_url is a webhook endpoint your pipeline infrastructure exposes. When the job completes, the TMS posts a status payload to this URL, which triggers the next pipeline stage. For systems that do not support webhooks, fall back to polling with exponential backoff.
Blocking Merges on Missing Coverage
The pipeline should post a commit status check (on GitHub) or an external status (on GitLab) that blocks the pull request from merging until all target locales meet a coverage threshold.
- name: Check translation coverage
run: |
COVERAGE=$(python scripts/check_coverage.py --source src/locales/en --targets src/locales)
if [ "$COVERAGE" != "100" ]; then
echo "Translation coverage is ${COVERAGE}%. Blocking merge."
exit 1
fi
On GitHub, this step's exit code determines the status check result. You can also use the GitHub Checks API to post richer annotations showing exactly which keys are missing in which locales.
GitHub Actions Pipeline: Full YAML Example
Below is a complete GitHub Actions workflow that ties together detection, submission, polling, validation, and commit-back.
name: Localization Pipeline
on:
push:
paths:
- 'src/locales/en/**'
permissions:
contents: write
checks: write
jobs:
localize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Detect changed strings
id: diff
run: |
CHANGED=$(git diff --name-only HEAD~1 HEAD -- 'src/locales/en/')
if [ -z "$CHANGED" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Upload to translation API
if: steps.diff.outputs.skip == 'false'
env:
TMS_TOKEN: ${{ secrets.TMS_API_TOKEN }}
PROJECT_ID: ${{ secrets.TMS_PROJECT_ID }}
run: |
for f in $(git diff --name-only HEAD~1 HEAD -- 'src/locales/en/'); do
curl -sf -X POST "https://api.example-tms.com/v2/projects/${PROJECT_ID}/files" \
-H "Authorization: Bearer ${TMS_TOKEN}" \
-F "file=@${f}" \
-F "update_strategy=merge"
done
- name: Poll for completion
if: steps.diff.outputs.skip == 'false'
env:
TMS_TOKEN: ${{ secrets.TMS_API_TOKEN }}
PROJECT_ID: ${{ secrets.TMS_PROJECT_ID }}
run: |
RETRIES=0
MAX_RETRIES=30
while [ $RETRIES -lt $MAX_RETRIES ]; do
STATUS=$(curl -sf -H "Authorization: Bearer ${TMS_TOKEN}" \
"https://api.example-tms.com/v2/projects/${PROJECT_ID}/jobs/latest" \
| jq -r '.status')
if [ "$STATUS" = "completed" ]; then
echo "Translations ready."
break
fi
RETRIES=$((RETRIES + 1))
sleep $((RETRIES * 10))
done
if [ "$STATUS" != "completed" ]; then
echo "Translation timed out after ${MAX_RETRIES} retries."
exit 1
fi
- name: Download translations
if: steps.diff.outputs.skip == 'false'
env:
TMS_TOKEN: ${{ secrets.TMS_API_TOKEN }}
PROJECT_ID: ${{ secrets.TMS_PROJECT_ID }}
run: |
for locale in fr de ja es; do
curl -sf -H "Authorization: Bearer ${TMS_TOKEN}" \
"https://api.example-tms.com/v2/projects/${PROJECT_ID}/export/${locale}" \
-o "src/locales/${locale}/messages.json"
done
- name: Validate coverage
run: python scripts/check_coverage.py --source src/locales/en --targets src/locales --threshold 100
- name: Commit translations
if: steps.diff.outputs.skip == 'false'
run: |
git config user.name "localization-bot"
git config user.email "bot@example.com"
git add src/locales/
git diff --cached --quiet || git commit -m "chore: update translations [skip ci]"
git push
Key details:
- [skip ci] in the commit message prevents an infinite loop where the bot's commit retriggers the pipeline.
- fetch-depth: 2 ensures HEAD~1 is available for the diff.
- Exponential backoff in the polling step (sleep $((RETRIES * 10))) avoids hammering the API.
GitLab CI Pipeline: Full YAML Example
The GitLab equivalent uses .gitlab-ci.yml stages and GitLab's built-in variables for diff detection.
stages:
- detect
- translate
- validate
- commit
variables:
TMS_PROJECT_ID: $TMS_PROJECT_ID
detect_changes:
stage: detect
script:
- |
CHANGED=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA -- 'src/locales/en/')
if [ -z "$CHANGED" ]; then
echo "NO_CHANGES" > status.txt
else
echo "HAS_CHANGES" > status.txt
fi
artifacts:
paths:
- status.txt
upload_strings:
stage: translate
needs: [detect_changes]
rules:
- exists:
- status.txt
script:
- |
[ "$(cat status.txt)" = "NO_CHANGES" ] && exit 0
for f in $(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA -- 'src/locales/en/'); do
curl -sf -X POST "https://api.example-tms.com/v2/projects/${TMS_PROJECT_ID}/files" \
-H "Authorization: Bearer ${TMS_API_TOKEN}" \
-F "file=@${f}" \
-F "update_strategy=merge"
done
- python scripts/poll_and_download.py --project ${TMS_PROJECT_ID} --locales fr,de,ja,es
validate_coverage:
stage: validate
needs: [upload_strings]
script:
- python scripts/check_coverage.py --source src/locales/en --targets src/locales --threshold 100
commit_translations:
stage: commit
needs: [validate_coverage]
script:
- git config user.name "localization-bot"
- git config user.email "bot@ci.example.com"
- git add src/locales/
- git diff --cached --quiet || git commit -m "chore: update translations [skip ci]"
- git push "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:${CI_COMMIT_REF_NAME}
GitLab requires an explicit push URL with a project access token (GITLAB_TOKEN) stored in CI/CD variables. The [skip ci] commit message convention also works here.
Environment Separation: Dev, Staging, Production
Not every translation belongs in every environment. A common pattern:
| Environment | Translation quality | Source branch | Behavior |
|---|---|---|---|
| Dev | Raw machine translation | Feature branches | Auto-merge, no review gate |
| Staging | MT + automated QA checks | develop / main | Block on coverage; flag QA issues |
| Production | Reviewed / human-verified | release/* or tags | Block on review sign-off |
In the pipeline, branch detection controls which workflow is invoked:
- name: Select translation workflow
run: |
if [[ "${{ github.ref }}" == refs/heads/release/* ]]; then
echo "workflow=human_review" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "workflow=mt_plus_qa" >> $GITHUB_OUTPUT
else
echo "workflow=mt_only" >> $GITHUB_OUTPUT
fi
This ensures developers get fast feedback on feature branches while production releases go through rigorous quality review.
Branch-to-Locale Mapping Strategies
Some teams use branch naming conventions to control which locales are translated. For example, a feature/checkout-ja branch might only trigger Japanese translation, while main triggers all target locales. This is implemented by maintaining a mapping file:
{
"branch_patterns": {
"feature/*-ja": ["ja"],
"feature/*-latam": ["es-419", "pt-BR"],
"main": ["fr", "de", "ja", "es", "pt-BR", "zh-Hans"],
"release/*": ["fr", "de", "ja", "es", "pt-BR", "zh-Hans", "ko", "ar"]
}
}
The pipeline reads this file and passes the resolved locale list to the translation API call. This avoids burning API quota on locales that are irrelevant to a given branch's scope.
Secrets Management for API Tokens
Translation API tokens are sensitive credentials. Follow these practices:
- Store tokens in your CI platform's encrypted secrets store (GitHub Actions secrets, GitLab CI/CD variables marked as masked and protected).
- Scope tokens to the minimum required permission. A token that can upload and download files should not have project deletion rights.
- Rotate tokens on a schedule. Use short-lived tokens where the API supports them.
- Never log token values. Mask them in CI output by referencing them only through environment variables.
For teams managing multiple translation providers, a secrets manager like HashiCorp Vault or AWS Secrets Manager can centralize credential rotation and audit logging.
Ready to see Ollang in action?
Talk to our team about your localization goals and see how the Ollang platform fits your workflow.
Webhooks, Status Checks, and Artifact Generation
Posting Translation Status as Commit Checks
Rather than polling, a webhook-driven flow is more efficient and responsive. Your pipeline exposes a webhook endpoint (or uses a serverless function) that the translation API calls when a job finishes:
# Minimal Flask webhook receiver
@app.route("/hooks/translation-complete", methods=["POST"])
def translation_webhook():
payload = request.json
job_id = payload["job_id"]
status = payload["status"]
if status == "completed":
# Trigger downstream pipeline via GitHub API
trigger_github_workflow(
repo="org/product",
workflow="download-translations.yml",
ref=payload["branch"],
inputs={"job_id": job_id}
)
return "", 200
if status == "failed":
post_github_commit_status(
repo="org/product",
sha=payload["commit_sha"],
state="failure",
description=f"Translation job {job_id} failed"
)
return "", 200
This approach eliminates idle polling minutes from your pipeline and gives developers immediate feedback in their pull request.
Generating Compiled Resource Bundles as Artifacts
The final pipeline step should produce build-ready artifacts. For mobile apps, this might mean compiling .strings (iOS) or strings.xml (Android) files. For web apps, it could mean generating optimized JSON chunks per locale for lazy loading.
- name: Build locale bundles
run: |
for locale in $(ls src/locales/); do
node scripts/compile-messages.js --locale $locale --out dist/locales/${locale}.json
done
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: locale-bundles
path: dist/locales/
These artifacts can then be consumed by downstream deployment jobs, CDN upload steps, or mobile build pipelines.
Idempotency, Retries, and Error Handling
Translation API calls must be safe to retry. Network failures, rate limits, and transient server errors are inevitable in any CI pipeline.
Making API Calls Idempotent
Idempotency means that submitting the same request multiple times produces the same result as submitting it once. Key techniques:
- Use update_strategy=merge or equivalent upsert semantics when uploading files. This prevents duplicate keys.
- Include a client-generated idempotency key in request headers if the API supports it.
- Design your diff logic so that re-running the pipeline on the same commit produces the same set of changed keys.
Retry with Exponential Backoff
import time
import requests
def call_with_retry(url, headers, payload, max_retries=5):
for attempt in range(max_retries):
try:
resp = requests.post(url, headers=headers, json=payload, timeout=30)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
resp.raise_for_status()
return resp.json()
except requests.exceptions.RequestException:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
Respect the Retry-After header when present. For 5xx errors, retry; for 4xx errors (other than 429), fail immediately, retrying a malformed request wastes time.
Handling Timeouts and Partial Failures
Set explicit timeouts on every HTTP call. A translation job that takes longer than expected should not block your pipeline indefinitely. Define a maximum wall-clock time for the entire localization stage and fail with a clear error message if exceeded.
For partial failures, where some locales complete but others do not, decide on a policy:
- Strict: Block the merge until all locales are complete.
- Lenient: Merge with available translations and open an issue for missing locales.
- Tiered: Apply strict policy for Tier 1 locales (e.g., French, German, Japanese) and lenient for Tier 2.
Rollbacks for Bad Translations
Automated translation can produce incorrect output. A rollback strategy ensures bad translations do not reach users.
Git-Based Rollback
Since translated files are committed to the repository, rolling back is a standard git revert:
# Revert the last translation commit
git revert --no-edit $(git log --oneline --grep="chore: update translations" -1 --format="%H")
git push
Tag each translation commit with metadata (job ID, provider, timestamp) so you can trace which translation run introduced a problem.
Translation Memory Rollback
If your TMS maintains translation memory, a bad batch can pollute future translations. After reverting in Git, also purge or flag the affected TM entries via the TMS API:
curl -X DELETE "https://api.example-tms.com/v2/projects/${PROJECT_ID}/tm/entries" \
-H "Authorization: Bearer ${TMS_TOKEN}" \
-d '{"job_id": "job_xyz789"}'
Not all TMS platforms support granular TM deletion. In those cases, mark the entries for review rather than automatic reuse.
TMS APIs vs. Direct Provider APIs
Choosing between a TMS API and a direct translation provider API is one of the most consequential decisions in your pipeline architecture. Ollang adds a third option: an orchestration layer that unifies text, documents, audio, and video workflows behind a single API and webhook model.
| Criterion | Ollang Orchestration API | TMS API (e.g., Phrase, Lokalise) | Direct Provider API (e.g., DeepL, Google) |
|---|---|---|---|
| Translation memory | Leverages provider/TMS TM where available; maintains consistency via orchestration and reuse across assets | Built-in, auto-leveraged within the TMS | You manage TM externally or not at all |
| Glossary enforcement | Centralized glossary enforcement across text, docs, and media; routed to compatible engines | Native, per-project | Supported but manual setup per request/provider |
| Human review workflow | Built-in quality review and human-in-the-loop options | Integrated assignment and review stages | Not included; you must build it |
| File/format support | Broad: JSON, XLIFF, PO, Markdown, docs (PDF/Office), SRT/VTT, plus audio/video pipelines | Broad for software strings; documents vary by vendor | Typically plain text or HTML segments |
| Latency | Adaptive: synchronous MT for small diffs; async jobs for large/human-review tasks | Higher (job queue, routing) | Lower (synchronous MT response) |
| Cost model | Orchestration layer; depends on chosen workflows/providers; single integration point | Per-word or subscription | Per-character or per-request |
| Multi-asset handling | Native across software strings, documents, subtitles, audio, and video | Varies; most focus on software strings | Text only |
In short, TMS APIs excel when you need structured human review and TM leverage on software strings; direct MT is ideal for fast, machine-only passes; and Ollang covers end-to-end, multi-asset localization with a single API and built-in review, reducing the number of integrations you must own.
Need help deciding between TMS, direct MT, or orchestration? Compare options with an engineer: Get an architecture review.
Where Ollang Fits: Orchestration Across Multi-Asset Types
Most TMS platforms and direct MT APIs are optimized for a single content type, typically software strings in standard resource file formats. But enterprise localization rarely stops there. A single product release might include:
- UI strings in JSON or XLIFF
- API documentation in Markdown
- Video tutorials requiring subtitle localization
- Audio content needing voiceover or dubbing
- Legal documents with strict terminology requirements
- Marketing website pages
Ollang operates as an AI execution layer that orchestrates translation across all of these asset types through a unified API surface. Rather than wiring separate pipelines to separate providers for text, video, audio, and document localization, you connect your CI/CD pipeline to Ollang and let it route each asset type to the appropriate translation workflow, including built-in translation quality review.
This matters in a CI/CD context because a single commit might touch both a .json resource file and a subtitle .srt file. A pipeline connected to Ollang can submit both in the same job, apply consistent glossary and terminology controls, and receive results through the same webhook, without maintaining parallel integrations. It reduces the number of pipeline integrations, credential sets, and failure modes engineering teams must operate.
If you'd like an architecture review of how Ollang would fit into your CI/CD localization flow, schedule a working session.
| Capability | Ollang | Typical TMS | Direct MT API |
|---|---|---|---|
| Software string localization | ✅ | ✅ | ✅ |
| Document localization | ✅ | Limited | ❌ |
| Video/subtitle localization | ✅ | ❌ | ❌ |
| Audio localization | ✅ | ❌ | ❌ |
| Legal document localization | ✅ | ❌ | ❌ |
| Website localization | ✅ | Partial | ❌ |
| Built-in quality review | ✅ | Varies | ❌ |
| Unified API for all asset types | ✅ | ❌ | ❌ |
Ollang's multi-modal coverage lets engineering teams maintain a single integration rather than stitching together point solutions. For organizations localizing across software, content, and media, consolidating to one orchestration layer reduces configuration and operational overhead.
Frequently Asked Questions
How do I prevent the localization bot's commits from triggering an infinite pipeline loop?
Include [skip ci] (or your platform's equivalent, such as [ci skip] for GitLab) in the bot's commit message. Both GitHub Actions and GitLab CI recognize this convention and will not trigger a new pipeline run for that commit. As an additional safeguard, add a path filter to your workflow so it only triggers on changes to the source locale directory, not the target locale directories.
Should I use synchronous or asynchronous translation API calls in my pipeline?
For small payloads (under a few hundred strings), synchronous calls are simpler and faster. For larger batches or when human review is part of the workflow, asynchronous patterns with webhook callbacks are more reliable. Async calls also avoid tying up a CI runner while waiting for translation to complete. Most TMS APIs are inherently asynchronous; direct MT APIs often support both modes. Ollang supports async webhook-driven flows suitable for CI integration.
How do I handle placeholder and markup preservation in translated strings?
Define placeholders using a consistent syntax (e.g., {variable_name} or {{slot}}) and validate that translated strings contain the same placeholders as the source. Include a validation step in your pipeline that parses both source and target strings and fails if any placeholders are missing, added, or reordered. For HTML or XML markup, use the translation API's markup-aware mode if available, and validate well-formedness of the output.
What coverage threshold should I set for blocking merges?
This depends on your locale tier strategy. Many teams require 100% coverage for Tier 1 locales (markets representing the majority of revenue) and 90% or higher for Tier 2 locales. The threshold should be configurable per environment: dev branches might allow 0% (no gate), staging might require 95%, and production releases require 100% with human review sign-off.
Start Automating Your Localization Pipeline
A well-designed CI/CD localization pipeline eliminates the manual handoffs, string drift, and release delays that plague multilingual software delivery. The patterns in this article, diff detection, idempotent API calls, environment-tiered quality gates, webhook-driven status checks, and git-based rollbacks, give you a production-ready blueprint.
For teams that need to go beyond software strings and localize documentation, video, audio, and legal content through the same pipeline, Ollang provides the unified API and orchestration layer to make that practical.
Ready to see Ollang in action?
Talk to our team about your localization goals and see how the Ollang platform fits your workflow.
Next Steps
Ready to connect your repository to a unified localization pipeline across text, docs, audio, and video? Book a Demo
Published on August 13, 2026