Localization that depends on manual file handoffs, export, email, wait, re-import, creates a bottleneck that scales poorly as release cadences tighten. When your engineering team ships daily but translations arrive weekly, you either delay launches or ship incomplete experiences. The fix is to treat localization as a first-class CI/CD concern: extracting translatable strings from code automatically, pushing them to a translation layer via API, listening for completion webhooks, and pulling localized bundles back into the build, all without a human touching a file. This article walks through the concrete architecture, pipeline configuration, file-format handling, quality gates, and failure-recovery patterns you need to wire translation APIs into your existing repo-connected workflows.
If your team is ready to eliminate manual localization handoffs, explore how Ollang's API layer fits into your pipeline.
Why Localization Belongs in Your CI/CD Pipeline
The Cost of Manual Handoffs
Manual localization workflows introduce three compounding problems. First, context loss: developers extract strings into spreadsheets or ad-hoc files, stripping away the code context that translators need. Second, sync drift: by the time translations return, the source strings have changed, creating merge conflicts and orphaned keys. Third, invisible delays: localization becomes the long pole in the release tent, but because it happens outside the engineering workflow, it rarely shows up on sprint boards or velocity charts.
Industry analyses such as the Nimdzi 100 report show that localization is a large and growing market, yet a significant share of enterprise effort is still consumed by project management overhead rather than actual translation. Automating the handoff eliminates that overhead.
How API-Driven Workflows Remove Bottlenecks
An API-driven localization workflow mirrors what modern engineering already does with linting, testing, and deployment:
- Trigger on change. A commit or pull request that modifies source strings kicks off the localization job.
- Push via API. Resource files are sent to the translation layer programmatically, with metadata (branch, commit SHA, file format).
- Translate asynchronously. The translation engine, whether human, machine, or hybrid, processes the job without blocking the developer.
- Receive via webhook. A callback notifies the pipeline when translations are ready.
- Pull and build. The pipeline fetches localized bundles, runs quality checks, and produces artifacts.
This model treats translations like any other build dependency: fetched, validated, and bundled automatically. Ollang provides an API-first layer that supports webhook handling, manifest uploads, and metadata tracking to integrate these steps directly into CI/CD.
Extracting and Pushing Source Strings via API
Identifying Translatable Keys in Code
Reliable extraction starts with convention. Most frameworks already define how translatable strings are declared:
| Platform / Framework | Key Declaration Pattern | Resource File Format |
|---|---|---|
| Android | getString(R.string.key) | XML (res/values/strings.xml) |
| iOS / macOS | NSLocalizedString("key", …) | .strings or .stringsdict |
| React (react-intl) | <FormattedMessage id="key" /> | JSON |
| Java / Spring | messages.properties | Properties / XLIFF |
| Generic (ICU) | {count, plural, one {…} other {…}} | JSON / XLIFF |
Extraction tools like i18next-parser, genstrings, or Android's lint can scan source code and produce canonical resource files. The critical rule: every translatable string must have a stable key. Auto-generated keys based on the source text itself cause churn whenever copy changes.
Your extraction step should run as an early pipeline stage, producing a deterministic set of resource files that become the API payload.
Pushing Resource Files to the Translation API
Once you have clean resource files, the API call is straightforward. A typical request to a translation API endpoint looks like this:
curl -X POST https://api.translation-service.example/v1/jobs \
-H "Authorization: Bearer $TMS_API_TOKEN" \
-H "Content-Type: multipart/form-data" \
-F "file=@src/locales/en/messages.json" \
-F "source_language=en" \
-F "target_languages=de,fr,ja" \
-F "callback_url=https://ci.yourcompany.com/hooks/translation-complete" \
-F "reference=main/abc1234"
Key parameters to include:
- Source and target languages as BCP 47 tags.
- Callback URL for the webhook that signals completion.
- Reference metadata (branch name, commit SHA) so you can match the response to the right build.
- File format hint if the API doesn't auto-detect (e.g., xliff-2.0, android-xml, ios-strings).
For bulk operations, say, pushing dozens of resource files across multiple modules, batch the uploads in a single API call or use a manifest endpoint if available. This reduces round trips and keeps rate-limit consumption predictable.
Webhook-Driven Job Completion and Bundle Generation
Listening for Translation Webhooks
Asynchronous translation jobs need a reliable notification mechanism. Webhooks are the standard pattern. When the translation job finishes, the API sends an HTTP POST to your registered callback URL with a payload like:
{
"event": "job.completed",
"job_id": "j-98a3f1",
"reference": "main/abc1234",
"status": "complete",
"target_languages": ["de", "fr", "ja"],
"download_urls": {
"de": "https://api.translation-service.example/v1/jobs/j-98a3f1/download?lang=de",
"fr": "https://api.translation-service.example/v1/jobs/j-98a3f1/download?lang=fr",
"ja": "https://api.translation-service.example/v1/jobs/j-98a3f1/download?lang=ja"
}
}
Your webhook receiver should:
- Verify the signature. Most translation APIs sign webhook payloads with HMAC-SHA256. Validate before processing.
- Match the reference to a pending build or branch.
- Trigger the downstream pipeline stage (fetch, validate, bundle).
If your CI system doesn't natively support inbound webhooks (some hosted runners don't), use a lightweight relay: a serverless function that receives the webhook and triggers a pipeline via the CI system's own API (e.g., GitHub Actions repository_dispatch or GitLab pipeline triggers).
Generating Localized Bundles per Branch
Each branch should produce its own set of localized bundles. This prevents half-translated strings from a feature branch from leaking into the main build. The bundle generation step:
- Downloads translated files from the API for the specific branch reference.
- Places them in the correct directory structure (e.g., res/values-de/strings.xml for Android).
- Runs format validation (well-formed XML, valid JSON, correct .strings escaping).
- Commits the bundles back to the branch or stores them as build artifacts.
For monorepos with multiple apps, namespace the bundles by module to avoid key collisions.
Pipeline Configuration: GitHub Actions and GitLab CI Examples
Secrets Management and Environment Isolation
Never hard-code API tokens. Both GitHub Actions and GitLab CI provide encrypted secret storage.
GitHub Actions, store your translation API token as a repository secret (TMS_API_TOKEN) and reference it in the workflow:
env:
TMS_API_TOKEN: ${{ secrets.TMS_API_TOKEN }}
GitLab CI, use CI/CD variables with the "Masked" and "Protected" flags enabled, scoped to specific environments:
variables:
TMS_API_TOKEN: $TMS_API_TOKEN
Environment isolation matters because you typically want different behavior across contexts:
| Environment | Behavior |
|---|---|
| Feature branch | Push strings, receive translations, but don't publish bundles to production CDN |
| Staging | Full translation round-trip with QA review step |
| Production / main | Only accept bundles that passed all quality gates |
Use branch-based rules (if: github.ref == 'refs/heads/main' in GitHub Actions, rules: - if: $CI_COMMIT_BRANCH == "main" in GitLab) to gate which stages run.
When you’re ready to wire this into GitHub/GitLab, from secrets to webhooks and quality gates, get a walkthrough tailored to your stack.
Dry Runs and Preview Builds
A dry-run mode lets you validate the pipeline without actually submitting translation jobs or spending API credits. Implement it by adding a flag:
# GitHub Actions example
jobs:
localize:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract strings
run: npx i18next-parser
- name: Diff source strings
run: diff <(git show HEAD~1:src/locales/en/messages.json) src/locales/en/messages.json || true
- name: Push to translation API
if: env.DRY_RUN != 'true'
run: |
curl -X POST https://api.translation-service.example/v1/jobs \
-H "Authorization: Bearer $TMS_API_TOKEN" \
-F "file=@src/locales/en/messages.json" \
-F "source_language=en" \
-F "target_languages=de,fr,ja"
- name: Dry-run summary
if: env.DRY_RUN == 'true'
run: echo "Dry run, skipped API push. Changed keys listed above."
The diff step is valuable even outside dry runs: it shows reviewers exactly which strings changed, making PR reviews localization-aware.
