Agent Skills: Lokalise Deploy Integration

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/lokalise-deploy-integration

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/lokalise-pack/skills/lokalise-deploy-integration

Skill Files

Browse the full folder contents for lokalise-deploy-integration.

Download Skill

Loading file tree…

plugins/saas-packs/lokalise-pack/skills/lokalise-deploy-integration/SKILL.md

Skill Metadata

Name
lokalise-deploy-integration
Description
|

Lokalise Deploy Integration

Overview

Translations must be downloaded fresh during CI/CD builds to ensure production always ships the latest reviewed content. This skill covers downloading translations as a build step, GitHub Actions workflows for translation sync, Vercel and Netlify build plugin integration, OTA (over-the-air) updates for mobile apps via Lokalise's iOS and Android SDKs, and environment-specific translation bundles.

Prerequisites

  • Lokalise API token with download permissions (read-only token recommended for CI)
  • LOKALISE_API_TOKEN and LOKALISE_PROJECT_ID stored as CI secrets
  • curl and unzip available in CI environment (standard on GitHub Actions runners)
  • For OTA: Lokalise OTA SDK token (separate from API token, generated in Lokalise dashboard)

Instructions

1. Download Translations in the Build Step

Add a pre-build script that pulls translations from Lokalise before your framework compiles:

#!/bin/bash
# scripts/download-translations.sh
set -euo pipefail

PROJECT_ID="${LOKALISE_PROJECT_ID:?Missing LOKALISE_PROJECT_ID}"
API_TOKEN="${LOKALISE_API_TOKEN:?Missing LOKALISE_API_TOKEN}"
DEST_DIR="${1:-./src/locales}"

echo "Downloading translations for project $PROJECT_ID..."

BUNDLE_URL=$(curl -sf -X POST \
  "https://api.lokalise.com/api2/projects/${PROJECT_ID}/files/download" \
  -H "X-Api-Token: ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{
    \"format\": \"json\",
    \"original_filenames\": false,
    \"bundle_structure\": \"%LANG_ISO%.json\",
    \"export_empty_as\": \"base\",
    \"json_unescaped_slashes\": true,
    \"include_tags\": [\"production\"],
    \"filter_data\": [\"translated\", \"reviewed\"]
  }" | jq -r '.bundle_url')

if [ -z "$BUNDLE_URL" ] || [ "$BUNDLE_URL" = "null" ]; then
  echo "ERROR: Failed to get bundle URL from Lokalise"
  exit 1
fi

mkdir -p "$DEST_DIR"
curl -sfL "$BUNDLE_URL" -o /tmp/translations.zip
unzip -o /tmp/translations.zip -d "$DEST_DIR"
rm /tmp/translations.zip

FILE_COUNT=$(ls -1 "$DEST_DIR"/*.json 2>/dev/null | wc -l)
echo "Downloaded $FILE_COUNT translation files to $DEST_DIR"

Wire it into package.json:

{
  "scripts": {
    "prebuild": "./scripts/download-translations.sh ./src/locales",
    "build": "next build"
  }
}

2. GitHub Actions Workflow

Full workflow that downloads translations, builds, and deploys:

# .github/workflows/deploy.yml
name: Build & Deploy

on:
  push:
    branches: [main]
  # Trigger from Lokalise webhook (via repository_dispatch)
  repository_dispatch:
    types: [translations_updated]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Download translations from Lokalise
        env:
          LOKALISE_API_TOKEN: ${{ secrets.LOKALISE_API_TOKEN }}
          LOKALISE_PROJECT_ID: ${{ secrets.LOKALISE_PROJECT_ID }}
        run: |
          chmod +x ./scripts/download-translations.sh
          ./scripts/download-translations.sh ./src/locales

      - name: Verify translation integrity
        run: |
          # Ensure all expected languages are present
          EXPECTED_LANGS="en fr de ja es"
          for lang in $EXPECTED_LANGS; do
            if [ ! -f "./src/locales/${lang}.json" ]; then
              echo "ERROR: Missing translation file for ${lang}"
              exit 1
            fi
            # Validate JSON
            jq empty "./src/locales/${lang}.json" || {
              echo "ERROR: Invalid JSON in ${lang}.json"
              exit 1
            }
          done
          echo "All translation files present and valid"

      - name: Build
        run: npm run build

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: --prod

To trigger builds when translations change, set up a Lokalise webhook that fires a GitHub repository_dispatch:

# In your webhook handler (see lokalise-webhooks-events)
curl -X POST \
  "https://api.github.com/repos/OWNER/REPO/dispatches" \
  -H "Authorization: token ${GITHUB_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "translations_updated"}'

3. Vercel Build Integration

For Vercel, translations download during the build phase. Configure the token as an environment variable:

# Set Lokalise secrets in Vercel
vercel env add LOKALISE_API_TOKEN production preview
vercel env add LOKALISE_PROJECT_ID production preview

In vercel.json, ensure the build command runs the translation download:

{
  "buildCommand": "./scripts/download-translations.sh ./src/locales && next build",
  "outputDirectory": ".next"
}

For ISR/SSR apps that need translations at runtime (not just build time), cache translations in a KV store or download on cold start:

// lib/translations.ts (Next.js example)
import { unstable_cache } from "next/cache";

export const getTranslations = unstable_cache(
  async (locale: string) => {
    const res = await fetch(
      `https://api.lokalise.com/api2/projects/${process.env.LOKALISE_PROJECT_ID}/translations`,
      {
        headers: { "X-Api-Token": process.env.LOKALISE_API_TOKEN! },
      }
    );
    const data = await res.json();
    return data.translations
      .filter((t: any) => t.language_iso === locale)
      .reduce(
        (acc: Record<string, string>, t: any) => ({
          ...acc,
          [t.key_name]: t.translation,
        }),
        {}
      );
  },
  ["translations"],
  { revalidate: 3600, tags: ["translations"] }
);

4. Netlify Build Integration

Netlify uses build plugins or the prebuild command. The simplest approach uses netlify.toml:

# netlify.toml
[build]
  command = "./scripts/download-translations.sh ./src/locales && npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "20"

Set secrets via Netlify CLI:

netlify env:set LOKALISE_API_TOKEN "your-token" --scope builds
netlify env:set LOKALISE_PROJECT_ID "123456789.abcdefgh" --scope builds

For a custom Netlify Build Plugin that integrates more deeply:

// plugins/netlify-plugin-lokalise/index.js
module.exports = {
  async onPreBuild({ utils, constants }) {
    const { execSync } = require("child_process");
    try {
      console.log("Downloading translations from Lokalise...");
      execSync("./scripts/download-translations.sh ./src/locales", {
        stdio: "inherit",
        env: process.env,
      });
    } catch (error) {
      utils.build.failBuild("Failed to download translations from Lokalise");
    }
  },
};

5. OTA Updates for Mobile (iOS/Android)

Over-the-air updates let you push translation changes without an app store release. Lokalise provides native SDKs for this.

iOS (Swift):

// AppDelegate.swift
import Lokalise

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Initialize with OTA SDK token and project ID
    Lokalise.shared.setProjectID(
        "123456789.abcdefgh",
        token: "ota-sdk-token-from-lokalise-dashboard"
    )

    // Preemptively check for updates
    Lokalise.shared.checkForUpdates { updated, error in
        if let error = error {
            print("OTA update check failed: \(error.localizedDescription)")
            return
        }
        if updated {
            print("Translations updated OTA")
        }
    }

    return true
}

// Usage — works with NSLocalizedString automatically
let welcome = NSLocalizedString("welcome.title", comment: "Welcome screen title")

Android (Kotlin):

// Application.kt
import com.lokalise.sdk.Lokalise
import com.lokalise.sdk.LokaliseCallback

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        Lokalise.init(this)
        Lokalise.updateTranslations()

        // Optional: listen for update completion
        Lokalise.setUpdateCallback(object : LokaliseCallback {
            override fun onUpdated(oldBundleId: Long, newBundleId: Long) {
                Log.d("Lokalise", "Translations updated: $oldBundleId -> $newBundleId")
            }

            override fun onErrorOccurred(e: LokaliseException) {
                Log.e("Lokalise", "OTA update failed", e)
            }
        })
    }
}

// Usage — strings.xml values are overridden by OTA bundles
val welcome = getString(R.string.welcome_title)

Both SDKs fall back to the bundled translations if OTA download fails, so the app always has working strings.

6. Environment-Specific Translation Bundles

Use tags in Lokalise to manage environment-specific content:

# Download only production-tagged translations
./scripts/download-translations.sh ./src/locales  # uses "production" tag filter

# For staging: modify the script or use an env var
LOKALISE_TAGS="staging,beta" ./scripts/download-translations.sh ./src/locales

Update download-translations.sh to support dynamic tags:

# Add near the top of download-translations.sh
TAGS="${LOKALISE_TAGS:-production}"
TAG_JSON=$(echo "$TAGS" | jq -R 'split(",")' )

# Use in the curl payload:
# "include_tags": $TAG_JSON

This lets you maintain separate translation sets:

  • production — fully reviewed, stable translations
  • staging — includes new translations under review
  • beta — experimental copy for A/B testing

Output

  • CI/CD pipeline downloading fresh translations before every build
  • GitHub Actions workflow with translation validation and deployment
  • Vercel/Netlify configured with Lokalise secrets and build commands
  • Mobile apps receiving OTA translation updates without app store releases
  • Environment-specific bundles controlled by Lokalise tags

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | Missing translations in build | download-translations.sh failed silently | Use set -euo pipefail and check bundle URL | | Secret not found in CI | Env var not configured | Add via vercel env add / netlify env:set / GitHub Secrets | | Build timeout | Large project with many languages | Filter with filter_langs and include_tags | | OTA fails on device | Network blocked or token invalid | SDKs fall back to bundled translations automatically | | Stale translations in production | Cache not invalidated | Use repository_dispatch webhook to trigger rebuild | | Empty JSON files | No translations match tag filter | Verify tag names match between Lokalise and script |

Examples

Minimal GitHub Action (Translation Sync Only)

# .github/workflows/sync-translations.yml
name: Sync Translations
on:
  schedule:
    - cron: "0 */6 * * *"  # Every 6 hours
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Download translations
        env:
          LOKALISE_API_TOKEN: ${{ secrets.LOKALISE_API_TOKEN }}
          LOKALISE_PROJECT_ID: ${{ secrets.LOKALISE_PROJECT_ID }}
        run: |
          chmod +x ./scripts/download-translations.sh
          ./scripts/download-translations.sh ./src/locales

      - name: Commit if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add ./src/locales/
          git diff --cached --quiet || git commit -m "chore: sync translations from Lokalise"
          git push

Docker Build with Translations

# Dockerfile
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci

ARG LOKALISE_API_TOKEN
ARG LOKALISE_PROJECT_ID
COPY . .
RUN chmod +x ./scripts/download-translations.sh \
    && ./scripts/download-translations.sh ./src/locales \
    && npm run build

FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
RUN npm ci --production
EXPOSE 3000
CMD ["npm", "start"]

Build with: docker build --build-arg LOKALISE_API_TOKEN=$TOKEN --build-arg LOKALISE_PROJECT_ID=$PID .

Resources

Next Steps

For handling errors during API calls in your pipeline, see lokalise-common-errors. For managing translation data formats and encoding, see lokalise-data-handling.