Most localization pipelines break at the handoff. Developers push new strings to a repository, then someone manually uploads files to a translation management system, waits for translations, downloads them, and opens a pull request. Every manual step introduces delay, drift, and the risk of shipping untranslated UI. Repo-connected localization eliminates that gap by wiring your CI/CD pipeline directly to TMS APIs so that every commit containing changed strings automatically triggers extraction, translation, and asset delivery, without anyone alt-tabbing to a dashboard. This article walks through the architecture, API patterns, file formats, webhook strategies, and failure modes you need to build a reliable, automated pipeline from commit to localized artifact using platforms like Ollang, Phrase, Smartling, Lokalise, Transifex, and Crowdin.
If your team is evaluating how to automate localization end-to-end across text, software, and content types, explore how Ollang's execution layer fits into your CI/CD workflow.
Why Repo-Driven Localization Matters
The Cost of Manual Handoffs Between Dev and L10n
Manual localization handoffs create a compounding tax on every release. A developer adds three new strings, but the localization manager doesn't notice until the next weekly sync. By then, the feature has shipped in English only, and the translated assets arrive two sprints later, if they arrive at all. According to the Globalization and Localization Association (GALA), translation delays are among the top reasons companies ship incomplete locale coverage.
The costs are concrete:
- Context loss. When strings are extracted days after they're written, developers have moved on and can't answer translator questions about meaning or placeholders.
- Merge friction. Manually committed translation files collide with ongoing development, producing merge conflicts in resource bundles that nobody wants to resolve.
- Incomplete coverage. Without automated diffing, new or changed strings slip through. Users in non-primary locales encounter raw keys or stale copy.
- Velocity drag. Teams that localize manually report spending significant engineering hours per release cycle on file wrangling rather than product work.
What "Shift-Left" Means for Localization
"Shift-left" is borrowed from testing culture: catch problems earlier in the pipeline, where they're cheaper to fix. Applied to localization, it means treating translated strings as build artifacts that are produced, validated, and merged as part of the same CI/CD pipeline that runs your linter and test suite.
In a shift-left model:
- String extraction happens at commit time, not release time.
- Translation requests are triggered by webhooks or pipeline stages, not calendar reminders.
- Translated assets are pulled back into the repo and validated before merge, not after deploy.
- Missing locales block the build the same way a failing test would.
This isn't aspirational, it's how mature engineering organizations handle localization at scale. The architecture relies on TMS APIs that support push, pull, async job management, and webhook notifications.
Architecture Overview: Git β CI β TMS API β Artifact
The core loop of a repo-connected localization pipeline has four stages:
- Commit. A developer pushes code containing new or changed strings to a feature branch.
- Extract and push. The CI pipeline detects changed resource files, diffs them against the previous state, and pushes new or modified strings to the TMS via API.
- Translate and review. The TMS routes strings through machine translation, human review, or both. When translation is complete, a webhook fires back to the CI system.
- Pull and merge. The pipeline pulls translated files from the TMS, validates placeholder integrity and file format correctness, and commits them back to the branch or opens a pull request.
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β Git ββββββΆβ CI/CD ββββββΆβ TMS API ββββββΆβ Review β
β Commit β β Pipelineβ β (Push) β β + MT β
ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ
β² β
β Webhook fires β
βββββββββββββββββββββββββββββββββββ
Pull translated assets
This architecture works with any CI system, GitHub Actions, GitLab CI, Jenkins, CircleCI, Bitbucket Pipelines, and any TMS that exposes push/pull endpoints and webhook registration.
String Extraction and Diff Strategies
Detecting Changed Keys Across Commits
Pushing your entire resource file to the TMS on every commit is wasteful and can re-trigger translations for strings that haven't changed. Instead, diff only the strings that were added, modified, or removed.
A straightforward approach uses git diff to identify changed resource files, then a lightweight script to compare keys:
# Identify changed resource files in this commit
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD -- 'src/locales/en/*.json')
for FILE in $CHANGED_FILES; do
# Extract only new or modified keys
python3 scripts/diff_keys.py \
--previous <(git show HEAD~1:"$FILE") \
--current "$FILE" \
--output /tmp/changed_keys.json
done
The diff_keys.py script compares the previous and current versions of the JSON file key by key, emitting only entries where the value changed or the key is new. Deleted keys can be flagged separately for cleanup in the TMS.
Handling JSON, YAML, XLIFF, and Other Formats
Different frameworks use different resource file formats. Your extraction and diffing logic needs to handle the formats your codebase uses:
| Format | Common In | Key Considerations |
|---|---|---|
| JSON (flat or nested) | React, Vue, Angular, Node.js | Nested keys need flattening for some TMS APIs; watch for key-path collisions |
| YAML | Rails, Spring Boot, Flutter | Indentation-sensitive; anchors and aliases can confuse naive parsers |
| XLIFF 1.2 / 2.0 | iOS, enterprise TMS interchange | XML-based; <source> and <target> elements carry translation pairs |
| .strings / .stringsdict | iOS / macOS native | Proprietary format; pluralization rules vary by locale |
| .properties | Java / Android (legacy) | Escape sequences for Unicode; no native nesting |
| .po / .pot | GNU gettext, Django, WordPress | Msgid/msgstr pairs; plural forms defined per locale |
| ARB | Flutter / Dart | JSON-based with @ metadata keys for descriptions and placeholders |
When diffing, parse each format into a normalized key-value structure before comparison. Libraries like i18next-parser for JSON or Python's polib for PO files handle format-specific parsing so your diff logic stays format-agnostic.
Placeholder Safety and Validation
Placeholders, {username}, {{count}}, %d, %@, are the most fragile part of any translated string. A translator or MT engine that drops, reorders, or corrupts a placeholder will cause runtime crashes or garbled output.
Build placeholder validation into your pipeline:
- Extract placeholders from the source string using a regex pattern appropriate to your format (e.g., \{[^}]+\} for ICU-style).
- After translation, verify that every placeholder present in the source also appears in the target, in a syntactically valid form.
- Fail the pipeline if a placeholder is missing or malformed, and flag the specific string and locale for review.
import re
def validate_placeholders(source: str, target: str) -> list[str]:
pattern = r'\{[^}]+\}'
source_ph = set(re.findall(pattern, source))
target_ph = set(re.findall(pattern, target))
missing = source_ph - target_ph
return [f"Missing placeholder: {p}" for p in missing]
This check should run as a gate before translated files are committed back to the repo.
Push/Pull Patterns via TMS APIs
Uploading Source Strings on Commit
Most TMS platforms expose a file upload or key-value push endpoint. The general pattern is:
- Authenticate with an API key or OAuth token.
- Identify the project and branch (some platforms support branch-aware projects natively).
- Upload the changed source file or post individual key-value pairs.
Here's a generic curl example for uploading a JSON resource file:
curl -X POST "https://api.example-tms.com/v2/projects/${PROJECT_ID}/upload" \
-H "Authorization: Bearer ${TMS_API_TOKEN}" \
-F "file=@src/locales/en/messages.json" \
-F "file_format=json" \
-F "update_translations=false" \
-F "tags=release-4.2"
Key parameters to control:
- update_translations: Set to false if you only want to push source strings without overwriting existing translations.
- tags or branch: Tag uploaded strings so you can pull translations for a specific release or feature branch.
- cleanup_mode: Some APIs let you mark keys not present in the upload as deprecated, which helps keep the TMS in sync with the repo.
Pulling Translated Files After Completion
Once translations are ready, signaled by a webhook or polled via a status endpoint, pull the translated files back:
curl -X GET "https://api.example-tms.com/v2/projects/${PROJECT_ID}/download" \
-H "Authorization: Bearer ${TMS_API_TOKEN}" \
-G -d "locale=fr" -d "file_format=json" \
-o src/locales/fr/messages.json
For projects with many locales, batch the downloads:
LOCALES="fr de ja ko pt-BR zh-Hans"
for LOCALE in $LOCALES; do
curl -s -X GET "https://api.example-tms.com/v2/projects/${PROJECT_ID}/download" \
-H "Authorization: Bearer ${TMS_API_TOKEN}" \
-G -d "locale=${LOCALE}" -d "file_format=json" \
-o "src/locales/${LOCALE}/messages.json"
done
Sync vs. Async: When to Poll, When to Wait
Some TMS APIs process uploads synchronously, the response includes the result immediately. Others return a job ID for asynchronous processing.
- Synchronous endpoints are simpler but can time out for large files. Use them when you're pushing small diffs (under a few hundred keys).
- Asynchronous endpoints return immediately with a job ID. You then either:
- Poll the job status endpoint at intervals until completion.
- Register a webhook that the TMS calls when the job finishes.
Polling example:
# Submit async job
JOB_ID=$(curl -s -X POST ".../upload" ... | jq -r '.data.job_id')
# Poll until complete
while true; do
STATUS=$(curl -s -H "Authorization: Bearer ${TMS_API_TOKEN}" \
"https://api.example-tms.com/v2/jobs/${JOB_ID}" | jq -r '.data.status')
if [ "$STATUS" = "completed" ]; then break; fi
if [ "$STATUS" = "failed" ]; then echo "Job failed"; exit 1; fi
sleep 10
done
Webhooks are preferred for production pipelines because they eliminate wasted poll cycles and reduce API call volume against rate limits.
Branch-Based Workflows and Merge Strategies
Feature-Branch Isolation for In-Progress Translations
Branch-based localization prevents half-finished translations from leaking into your main branch. The pattern:
- Developer creates feature/onboarding-v2 and adds new strings.
- CI pushes those strings to the TMS, tagged with the branch name.
- Translations proceed in the TMS against that branch's string set.
- When translations are complete, CI pulls them into the same feature branch.
- The PR now includes both the code changes and the corresponding translations.
This keeps main clean, only fully translated features get merged. Some TMS platforms (Phrase, Crowdin) have native branch support. Others require you to simulate branches with tags or separate project keys. Ollang supports branch-aware workflows via branch metadata and tags exposed through its API.
Resolving Merge Conflicts in Resource Files
Resource file merge conflicts are the most common failure mode in repo-connected localization. Two branches modify the same locale file, and Git can't auto-merge.
Mitigation strategies:
- Sort keys deterministically. If your JSON or YAML keys are always alphabetically sorted, Git's merge algorithm handles concurrent additions cleanly in most cases.
- Use one-key-per-line formatting. Compact JSON with multiple keys on one line maximizes conflict surface. Pretty-printed, sorted JSON minimizes it.
- Automate conflict resolution. A post-merge script can parse both sides of a conflict, merge the key-value maps programmatically, and write a clean file. Since resource files are structured data, this is safer than text-level merge.
- Adopt a single source of truth. If the TMS is authoritative for translations, always overwrite local translations with the TMS version on pull. Conflicts only arise in the source language file, which the developer controls.
Webhook-Driven Translation Triggers
Firing MT + Review on Every Commit
Webhooks are the nervous system of a repo-connected pipeline. Configure two directions:
- Git β CI β TMS (outbound): Your CI platform fires on push events. The pipeline extracts changed strings and pushes them to the TMS API. This is typically handled by the CI YAML configuration, not a webhook per se, it's event-driven via the CI trigger.
- TMS β CI (inbound): Register a webhook in your TMS that fires when a translation job completes, a review is approved, or a specific status changes. The webhook payload hits an endpoint you control (a serverless function, a CI webhook trigger, or a dedicated microservice) that kicks off the pull-and-merge stage.
Example webhook payload from a TMS:
{
"event": "translation.completed",
"project_id": "abc123",
"locale": "de",
"branch": "feature/onboarding-v2",
"completed_keys": 47,
"timestamp": "2025-01-15T14:32:00Z"
}
Your handler parses this, triggers a CI pipeline run for the specified branch, and the pipeline pulls the German translations and commits them.
Configuring Webhook Endpoints Securely
Webhook endpoints are attack surface. Protect them:
- Validate signatures. Most TMS platforms sign webhook payloads with an HMAC secret. Verify the signature before processing.
- Use HTTPS only. Never expose a webhook receiver over plain HTTP.
- Scope permissions. The webhook handler should have the minimum permissions needed, typically read from the TMS and write to the repo.
- Idempotency. Webhooks can fire multiple times for the same event. Design your handler to be idempotent, pulling translations that are already up to date should be a no-op, not a duplicate commit.
