In this guide, we’ll walk through building a fully automated translation pipeline using GitLab CI/CD and Lokalise. From upload to download, with tagging, version control, and merge requests.
Here’s the high-level flow:
Manually uploading and downloading translations is annoying, error-prone, and doesn’t scale. This setup solves that by giving you:
Whether you’re a solo dev or managing a whole frontend team, this workflow keeps your translations in sync and your repo clean.
Let’s build it step by step!
Before integrating Lokalise with your GitLab CI/CD pipeline, make sure you have the following in place:
To manage and translate your content, you’ll first need to create a Lokalise project. This project will serve as your central hub for storing translation keys and their corresponding values.
In this guide, we’ll be working with JSON files using a basic key-value structure. Lokalise supports a wide range of formats, but JSON is perfect for our example. The goal is to upload these files, then automatically translate them into one or more target languages using Lokalise’s AI-powered tasks.
To get started, log in to your Lokalise account and head to the Projects dashboard. Click New project to create one.
Make sure to select the Web and mobile project type. It supports JSON and most other common formats devs typically work with.
Next, give your project a name and choose a base language — the one you’ll be translating from (we’ll use English in this guide). Then, pick one or more target languages (let’s say French for now, you can add more later).
Once that’s set, click Create project. Lokalise will take you to the project editor. From there, go to More > Settings in the top menu.
On the Settings page, locate your Project ID and take a note of it. We’ll need it in a moment.
Since we’ll be using Lokalise’s API to upload and download translations, you’ll need an API token with read/write permissions. To get it, click on your avatar in the left-hand menu and go to your Personal profile.
Switch to the API tokens tab and generate a new token. Be sure to give it read/write access.
Keep this token secret. Never commit it to version control or share it publicly.
Next, head over to your GitLab project and go to Settings > CI/CD. Scroll down to the Variables section and click Add variable.
You’ll need to add the following two variables:
These variables will be injected into your pipeline jobs and used by the Lokalise CLI.
As mentioned earlier, Lokalise supports a wide variety of file formats — including JSON, YAML, XML, and more — so you’re free to organize your translation files however it suits your project.
For the purposes of this guide, we’ll keep things simple and use a basic JSON-based setup. We’ll create two English translation files stored under /locales/en/.
This structure is just for demonstration — feel free to adapt it to your actual project later.
Create the following folder and files:
/locales/en/en.json
/locales/en/main.json
en.json
{
"app_name": "Awesome App",
"welcome": "Welcome to Awesome App!",
"logout": "Log out",
"error_generic": "Something went wrong. Please try again."
}
main.json
{
"home_title": "Home",
"home_subtitle": "Start your journey here",
"cta_get_started": "Get started",
"cta_learn_more": "Learn more",
"footer_rights": "All rights reserved © 2025"
}
Later in this guide, we’ll upload these files to Lokalise and retrieve their translations through GitLab CI/CD.
Now that we have our Lokalise project, API token, and translation files ready, it’s time to automate the upload process using GitLab CI/CD.
To do that, we’ll configure a pipeline that installs the Lokalise CLI and runs a custom script to upload our translation files. This gives you a repeatable, one-click way to sync your latest translations with Lokalise — no manual uploads needed.
With the pipeline config in place, we need to define the logic for uploading your translation files to Lokalise. We’ll do that in a shell script.
Create a new file at: /scripts/upload_translations.sh.
Paste the following content into the file:
#!/bin/bash
set -euo pipefail
: "${LOKALISE_API_TOKEN:?Need to set LOKALISE_API_TOKEN}"
: "${LOKALISE_PROJECT_ID:?Need to set LOKALISE_PROJECT_ID}"
: "${CI_COMMIT_REF_NAME:=unknown-branch}"
BIN_DIR="./bin"
LOKALISE_CLI="$BIN_DIR/lokalise2"
BRANCH_TAG="${CI_COMMIT_REF_NAME}"
if [[ ! -x "$LOKALISE_CLI" ]]; then
echo "❌ Lokalise CLI not found $LOKALISE_CLI"
exit 1
fi
if [[ ! -d "locales/en" ]]; then
echo "❌ Directory 'locales/en' not found!"
exit 1
fi
echo "🚀 Uploading from locales/en/..."
echo "📛 Using tag: $BRANCH_TAG"
find locales/en -name '*.json' | xargs -P 6 -I{} env FILE="{}" BRANCH_TAG="$BRANCH_TAG" bash -c '
echo "📤 Uploading $FILE..."
'"$LOKALISE_CLI"' \
--token='"$LOKALISE_API_TOKEN"' \
--project-id='"$LOKALISE_PROJECT_ID"' \
file upload \
--file="$FILE" \
--lang-iso="en" \
--include-path \
--poll \
--tag-inserted-keys \
--tags="gitlab,$BRANCH_TAG"
echo "✅ Uploaded: $FILE"
'
echo "🎉 All files uploaded."
Let’s break this down step by step:
Every uploaded key gets tagged with two tags:
This is especially handy in PR/merge request workflows where you want to keep translations isolated per feature or fix.
In the root of your project, create a file named .gitlab-ci.yml. This file defines the CI/CD pipeline GitLab will run when you trigger a job.
Here’s the base config to get started:
stages:
- lokalise
upload_translations:
stage: lokalise
script:
- curl -s https://raw.githubusercontent.com/lokalise/lokalise-cli-2-go/ccade804d8157994195498b6bc070180c2c87719/install.sh > install.sh
- chmod +x install.sh
- ./install.sh
- ./bin/lokalise2 --version
- chmod +x ./scripts/upload_translations.sh
- ./scripts/upload_translations.sh
when: manual
Let’s break it down:
The job is marked as manual, meaning it won’t run automatically on every commit. You’ll trigger it explicitly from the GitLab UI when you want to sync your latest translations.
The job uses environment variables (LOKALISE_PROJECT_ID and LOKALISE_API_TOKEN) which you’ve already set up in your .env file and GitLab settings.
Once you’ve added your .gitlab-ci.yml and the upload_translations.sh script, you’re ready to run the pipeline and upload your translation files to Lokalise.
Here’s how to do it. First, commit and push your pipeline setup to your GitLab repo:
git add .
git commit -m "Add Lokalise upload pipeline"
git push origin your-branch-name
Make sure you push this to a branch that’s allowed to run pipelines.
Since we’ve marked the upload_translations job with when: manual, it won’t run automatically. This is intentional and you’ll run it when you’re ready to sync translations.
To trigger it:
GitLab will spin up a runner, install the Lokalise CLI, and execute your upload script. Here’s the result on Lokalise:
So you’ve uploaded your source translation files to Lokalise, the AI elves (or humans, no judgment) have done their thing, and now it’s time to pull those translations back into your codebase.
Just like with uploads, we’ll automate this using GitLab CI/CD and a simple shell script.
Now let’s write the script that pulls translated files from Lokalise and opens a merge request in GitLab with the updated translations.
Create a new file at:
/scripts/download_translations.sh
Paste in the following content:
#!/bin/bash
set -euo pipefail
: "${LOKALISE_API_TOKEN:?Need to set LOKALISE_API_TOKEN}"
: "${LOKALISE_PROJECT_ID:?Need to set LOKALISE_PROJECT_ID}"
: "${GITLAB_TOKEN:?Need to set GITLAB_TOKEN}"
: "${CI_PROJECT_ID:?}"
: "${CI_REPOSITORY_URL:?}"
: "${CI_DEFAULT_BRANCH:?}"
BIN_DIR="./bin"
LOKALISE_CLI="$BIN_DIR/lokalise2"
BRANCH_TAG="${CI_COMMIT_REF_NAME:-manual}"
echo "📛 Filtering with: $BRANCH_TAG"
echo "📥 Downloading translations..."
$LOKALISE_CLI \
--token="$LOKALISE_API_TOKEN" \
--project-id="$LOKALISE_PROJECT_ID" \
file download \
--format=json \
--original-filenames=true \
--directory-prefix="/" \
--include-tags="gitlab,$BRANCH_TAG"
if git diff --quiet; then
echo "✅ No changes detected."
exit 0
fi
BRANCH="lokalise-sync-$(date +%Y%m%d%H%M%S)"
echo "🌿 Creating branch $BRANCH"
git config user.email "ci-bot@example.com"
git config user.name "Lokalise Bot"
git checkout -b "$BRANCH"
git add locales/
git commit -m "🔄 Update translations from Lokalise"
REPO_URL="https://oauth2:${GITLAB_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git"
git push "$REPO_URL" "$BRANCH"
echo "🚀 Creating Merge Request into $CI_DEFAULT_BRANCH"
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data "source_branch=$BRANCH" \
--data "target_branch=$CI_DEFAULT_BRANCH" \
--data "title=🔄 Update translations from Lokalise" \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests"
echo "✅ MR created."
Let’s unpack it:
The lokalise2 CLI is used to:
If any files changed:
Append this job to your existing .gitlab-ci.yml file:
download_translations:
stage: lokalise
script:
- curl -s https://raw.githubusercontent.com/lokalise/lokalise-cli-2-go/ccade804d8157994195498b6bc070180c2c87719/install.sh > install.sh
- chmod +x install.sh
- ./install.sh
- chmod +x ./scripts/download_translations.sh
- ./scripts/download_translations.sh
when: manual
Pretty much the same flow as the upload job:
We’ve marked it manual again, because you might not want to pull translations every time you push code — especially if you’re still tweaking copy or testing.
By default, GitLab provides a built-in CI_JOB_TOKEN that can be used to authenticate within the CI pipeline. However (and this is important) this token might not have sufficient permissions to create merge requests or push back to protected branches, especially if your project has strict permissions.
If you want to automatically commit downloaded translations or open merge requests from your pipeline (a common use case), you’ll likely need to create a personal access token with higher privileges.
So, you’ll need to:
write_repository
is mentioned for the sake of completeness; it might already be included in the api
scope):Make sure to mark it as masked and protected, just like the Lokalise token. To be extra safe, it might be a better idea to use project access tokens instead. Such tokens have access only to a single project.
After pulling translated files from Lokalise, you might want to tweak them before committing. Maybe you want to:
This is where post-processing comes in. You can add a post-processing block directly after the download step in your download_translations.sh script.
Here’s a simple example:
echo "🔧 Post-processing..."
find locales -name '*.json' | while read -r file; do
echo "✏️ Processing $file"
sed -i 's/App/Application/g' "$file"
done
It’s basic, but it shows the idea: you can apply any transformation to the downloaded files right before they go into a commit.
Real-world translations aren’t always perfect:
This step is your hook to make sure translations match your app’s expectations before they land in main. If things get more complex, feel free to replace this shell loop with a proper Node/TypeScript/Go script — whatever suits your stack.
The setup we built focuses on uploading base language files and pulling back translated ones but GitLab pipelines can do a lot more when paired with Lokalise.
Here are some additional workflows where automating via GitLab CI/CD makes your life easier:
Want to quickly test how your app looks in different languages — even before real translations are done?
You can create a manual GitLab job that uploads source strings to Lokalise, triggers machine translation using Lokalise’s AI features, and then pulls the translated files right back. Great for early l10n testing or UX review.
If you’re working on multiple platforms (like Android + iOS) or sharing common translation files across multiple services, a GitLab pipeline can regularly upload or download shared resource files between your repos and Lokalise ensuring consistency everywhere.
Set up a job that runs only after merging to main — automatically uploading any new keys or downloading fully translated strings, so production always has the freshest content.
This is especially useful when your team translates after features are merged.
Need to regularly sync translations without waiting for dev pushes? GitLab supports scheduled pipelines, so you can run translation downloads every day, week, or sprint-end.
You can even filter by translation status on Lokalise (e.g. only pull fully translated or reviewed keys).
Good translations depend on context. Lokalise lets you upload and link screenshots to keys via CLI — so why not automate that?
Create a job that watches a screenshots/ folder and automatically uploads new images to Lokalise, linking them to relevant keys. Especially handy for UI-heavy apps.
Already part of our core setup, but worth highlighting: using GitLab CI/CD to automatically tag uploaded keys by branch name gives you a powerful way to isolate content across features, hotfixes, or releases. Makes selective downloads a breeze.
These are just a few examples. With GitLab CI/CD and the Lokalise CLI, you’ve basically got a translation automation toolkit at your fingertips. Whether you’re syncing daily or controlling entire l10n workflows via scripts, it’s all doable.
Integrating Lokalise with GitLab CI/CD gives you full control over your localization workflow. You’ve now seen how to:
No more manual copy-pasting translations. No more “did we sync this file?” drama. Just clean, repeatable CI jobs that do the boring stuff so you can ship features, in every language.
Thank you for staying with me, and until next time!
Author
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Localization workflow for your web and mobile apps, games and digital content.
©2017-2025
All Rights Reserved.