Skip to content
Localization Planner

A CI tutorial for SaaS developers

How to Automate SaaS Localization with GitHub Actions

Translation files drift when copying them by hand. The SimpleLocalize CLI can upload your source strings and download translations as part of CI. This guide uses only flags and workflows found in the official documentation, and marks what you must verify yourself.

By SaaS Localization Planner · Reviewed

Disclosure and scope. This guide contains affiliate links to SimpleLocalize, marked as such; we may earn a commission if you sign up, at no extra cost to you. The sample workflow was parsed locally and its JSON key checker was exercised with valid and invalid fixtures. We have not run an authenticated upload or a GitHub-hosted workflow. Test in a disposable project before enabling source synchronization. Check your plan for CLI and auto-translation limits.

Why automate translation sync

Continuous localization means new and changed strings reach translators as part of normal development. A CLI step in CI makes that repeatable and removes manual file copying. If you have not defined owners and review levels yet, start with the SaaS localization checklist.

Install the CLI and get the API key

The CLI documentation lists macOS, Linux, Windows and an npm package. The macOS/Linux/WSL install command and usage pattern are:

curl -s https://get.simplelocalize.io/2.12/install | bash
simplelocalize [COMMAND] --apiKey YOUR_PROJECT_API_KEY

The project API key is under Settings, then Credentials, then API Key. If you do not have a project yet, you can create one in SimpleLocalize (affiliate link, new tab) and import a single language to try the CLI.

Affiliate link: we may earn a commission if you sign up, at no extra cost to you. This does not affect our rankings or recommendations.

Upload source strings

The upload command documents these flags: --apiKey, --uploadPath (supports {lang} and {ns}), --uploadFormat such as single-language-json, --uploadLanguageKey, --update, --dryRun and --preview. Without a language key, the documentation says only translation keys are imported without content. By default only empty translations are updated; --update updates existing ones.

simplelocalize upload \
  --apiKey YOUR_PROJECT_API_KEY \
  --uploadPath ./messages/en.json \
  --uploadFormat single-language-json \
  --uploadLanguageKey en \
  --dryRun

Variables and plural forms in JSON use your own i18n library’s syntax; see the single-language JSON format page. Our migration checklist explains how to verify placeholders and plurals after an import.

Download translations

The download command documents --downloadPath with {lang} and {ns} placeholders, --downloadFormat, --downloadLanguageKey for a comma-separated list of languages, --downloadTags and --downloadOptions such as WRITE_NESTED and ENABLE_FALLBACKS. Language keys must match those in the project’s Languages tab.

simplelocalize download \
  --apiKey YOUR_PROJECT_API_KEY \
  --downloadPath ./messages/{lang}.json \
  --downloadFormat single-language-json \
  --downloadLanguageKey de,fr,ja

GitHub Actions workflow

SimpleLocalize’s CI/CD page lists GitHub Actions, GitLab CI/CD, CircleCI, Travis CI and AWS CodeBuild, and provides an example using its own action. The manual workflow below uses the documented CLI commands, downloads into a separate directory and preserves files as an artifact for review. It uploads English source content to your translation project; it does not publish translations or commit to your repository. Create messages/en.json and a German target language first. Save the checker below before running it.

The source-only --update flag intentionally overwrites existing English values so copy changes propagate. First preview against a disposable project. Download is limited to German and uses WRITE_NESTED for the nested JSON example; adapt the target languages and format to your application.

name: Review translation update
on:
  workflow_dispatch:
permissions:
  contents: read
jobs:
  translations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install documented CLI release
        run: |
          curl -fsSL https://get.simplelocalize.io/2.12/install -o /tmp/install-simplelocalize.sh
          bash /tmp/install-simplelocalize.sh
      - name: Upload source messages
        env:
          SIMPLELOCALIZE_API_KEY: ${{ secrets.SIMPLELOCALIZE_API_KEY }}
        run: |
          simplelocalize upload \
            --apiKey "$SIMPLELOCALIZE_API_KEY" \
            --uploadPath ./messages/en.json \
            --uploadFormat single-language-json \
            --uploadLanguageKey en \
            --update
      - name: Download translations for review
        env:
          SIMPLELOCALIZE_API_KEY: ${{ secrets.SIMPLELOCALIZE_API_KEY }}
        run: |
          mkdir -p translation-review
          simplelocalize download \
            --apiKey "$SIMPLELOCALIZE_API_KEY" \
            --downloadPath './translation-review/{lang}.json' \
            --downloadFormat single-language-json \
            --downloadLanguageKey de \
            --downloadOptions WRITE_NESTED
      - name: Verify target keys
        run: node scripts/check-messages.mjs messages/en.json translation-review/de.json
      - uses: actions/upload-artifact@v4
        with:
          name: translations-for-review
          path: translation-review/*.json
          if-no-files-found: error

Save this checker as scripts/check-messages.mjs. It checks nested JSON key paths, not ICU grammar, placeholders or language quality. Copy the example source and target files from the Next.js workflow guide.

import {readFileSync} from 'node:fs';

function keys(value, path = '') {
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    throw new Error('Expected a JSON object at ' + (path || '/'));
  }
  return Object.entries(value).flatMap(([key, child]) => {
    if (key.includes('.') || key.includes('/')) {
      throw new Error('Use simple nested message keys: ' + key);
    }
    const next = path + '/' + key;
    if (typeof child === 'string') return [next];
    return keys(child, next);
  }).sort();
}

const [sourceFile, ...targets] = process.argv.slice(2);
if (!sourceFile || targets.length === 0) {
  throw new Error('Usage: node check-messages.mjs source.json target.json [...]');
}
const read = (file) => JSON.parse(readFileSync(file, 'utf8'));
const source = keys(read(sourceFile));
if (source.length === 0) throw new Error('Source has no messages');
for (const file of targets) {
  const target = keys(read(file));
  if (source.join('\n') !== target.join('\n')) {
    throw new Error('Message keys differ: ' + file);
  }
}
console.log('Message keys match');

The installer URL follows the vendor’s documented 2.12 release line; it is not an immutable binary pin. For production, review and pin an exact CLI release and full action commit SHAs under your dependency policy. Use repository secrets and retain the read-only repository permission. Missing translations should fail verification; inspect the artifact, review language quality, and submit a normal translation pull request before your deployment build. Automatic translation consumes a separate allowance and is intentionally not enabled in this example.

Safety checklist

  • Store the project API key as a CI secret; never commit it, print it in logs or put it in client-side code.
  • Pin the CLI version you tested instead of always taking the latest.
  • Run --dryRun or --preview before the first real upload, and read the output.
  • Upload only the source language from CI unless you intend to overwrite translations; use the update option deliberately.
  • Keep downloaded translation files in a reviewed pull request or a build artifact, not directly on your production branch without checks.
  • Respect the documented limit of 60 import and export requests per minute per project.

Verify the pipeline

  • Run the workflow in a branch and confirm the files land where your i18n library reads them.
  • Compare key counts and a sample of placeholders and plural forms with the source.
  • Change one source string and confirm it flows through to a target language.
  • Check that a missing translation shows the fallback you intend.
  • Rotate the API key and confirm the secret is the only place that needed updating.

For QA and release practices around translated files, follow the localization checklist. Comparing tools first? Read the translation management systems comparison. When you are ready to try it, open SimpleLocalize (affiliate link, new tab).

Affiliate link: we may earn a commission if you sign up, at no extra cost to you. This does not affect our rankings or recommendations.

Frequently asked questions

Where do I find the SimpleLocalize API key for the CLI?

The official CLI documentation says the project API key is under Settings, then Credentials, then API Key. Store it as a CI secret and never commit it.

Which CI services does SimpleLocalize document?

Its CI/CD page lists GitHub Actions, GitLab CI/CD, CircleCI, Travis CI and AWS CodeBuild, with detailed examples for GitHub Actions and AWS CodeBuild.

Does uploading overwrite existing translations?

By default the CLI updates only empty translations; the documented --update flag updates existing ones. Use --dryRun or --preview first.

Will auto-translation cost extra?

We could not confirm plan-specific details. Our catalog notes that auto-translation has separate character quotas, so check your plan before automating it.

Sources and disclosure

Official SimpleLocalize documentation checked on :

This guide contains affiliate links to SimpleLocalize. Affiliate relationships never affect tool rankings or the steps above. See our About page and privacy policy.