Claude-code-plugins-plus lokalise-cost-tuning
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/lokalise-pack/skills/lokalise-cost-tuning" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-lokalise-cost-tuning && rm -rf "$T"
plugins/saas-packs/lokalise-pack/skills/lokalise-cost-tuning/SKILL.mdLokalise Cost Tuning
Overview
Optimize Lokalise localization spending across plan tiers, contributor seats, Translation Memory (TM) leverage, machine translation (MT) triage, and dead key cleanup. Lokalise pricing is per-seat subscription (Essential ~$120/user/month, Pro ~$290/user/month) with optional pay-per-use for MT and AI features.
Prerequisites
- Lokalise Admin role for billing and usage visibility
with read access to project statisticsLOKALISE_API_TOKEN- Understanding of translation workflow (human, MT, or hybrid)
andcurl
for API queriesjq
Instructions
Step 1: Audit Current Usage
set -euo pipefail echo "=== Lokalise Usage Audit ===" # Get all projects with statistics PROJECTS=$(curl -sf "https://api.lokalise.com/api2/projects?limit=100&include_statistics=1" \ -H "X-Api-Token: ${LOKALISE_API_TOKEN}") echo "$PROJECTS" | jq -r '.projects[] | [.name, .statistics.keys_total, (.statistics.languages // [] | length), .statistics.progress_total] | @tsv' \ | column -t -s $'\t' -N "Project,Keys,Languages,Progress%" # Totals TOTAL_KEYS=$(echo "$PROJECTS" | jq '[.projects[].statistics.keys_total] | add') TOTAL_LANGS=$(echo "$PROJECTS" | jq '[.projects[] | (.statistics.languages // [] | length)] | max') PROJECT_COUNT=$(echo "$PROJECTS" | jq '.projects | length') echo "" echo "Totals: ${PROJECT_COUNT} projects, ${TOTAL_KEYS} keys, up to ${TOTAL_LANGS} languages" echo "" # Contributor count (seats = cost driver) TEAMS=$(curl -sf "https://api.lokalise.com/api2/teams" \ -H "X-Api-Token: ${LOKALISE_API_TOKEN}") echo "$TEAMS" | jq -r '.teams[] | "Team: \(.name) — \(.users_count) users (seats)"'
Step 2: Reduce Per-Seat Costs
Seats are the largest cost driver. Strategies to minimize:
import { LokaliseApi } from "@lokalise/node-api"; const lok = new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN! }); // Audit: Find inactive contributors (no activity in 90 days) async function findInactiveContributors(projectId: string): Promise<void> { const contributors = await lok.contributors().list({ project_id: projectId, limit: 500, }); console.log("=== Contributor Activity Audit ==="); for (const c of contributors.items) { const langs = c.languages .map((l: { lang_iso: string }) => l.lang_iso) .join(", "); console.log( `${c.fullname} <${c.email}> — ` + `admin: ${c.is_admin}, reviewer: ${c.is_reviewer}, ` + `languages: [${langs}]` ); } console.log(`\nTotal contributors: ${contributors.items.length}`); console.log( "Review: Remove freelancers between tasks. " + "Use contributor groups for batch management." ); } // Strategy: Use task-based access for freelance translators // - Add freelancers when a translation task opens // - Remove them when the task closes // - This avoids paying for idle seats // Cost example: 10 individual seats = ~$1,200/month // 3 permanent + task-based freelancers = ~$360/month
Step 3: Maximize Translation Memory (TM) Hits
TM matches reduce human translation volume. Keys with 100% TM match cost zero for translation.
// Strategy: Translate similar projects sequentially to build TM // Don't translate 3 apps in parallel — do one first, seed the TM, // then the others get 30-50% free matches on shared strings // Enable automations on upload to apply TM automatically const uploadResult = await lok.files().upload(projectId, { data: base64FileData, filename: "en.json", lang_iso: "en", use_automations: true, // Apply TM + MT suggestions replace_modified: true, detect_icu_plurals: true, }); // Check TM coverage after upload const languages = await lok.languages().list({ project_id: projectId, limit: 50 }); for (const lang of languages.items) { console.log( `${lang.lang_iso}: ${lang.statistics?.progress ?? 0}% translated, ` + `${lang.statistics?.words_to_do ?? "?"} words remaining` ); }
Step 4: Machine Translation Triage
Pre-translate low-risk content with MT. Reserve human translation for critical strings.
set -euo pipefail # Identify untranslated key volume per language curl -sf "https://api.lokalise.com/api2/projects/${LOKALISE_PROJECT_ID}/languages" \ -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \ | jq '.languages[] | { locale: .lang_iso, progress: .statistics.progress, words_to_do: .statistics.words_to_do }'
MT triage matrix — decide by key prefix:
| Key Prefix | Content Type | Translation Method | Cost Impact |
|---|---|---|---|
, | Tooltips, help text | Machine Translation | Low risk, high volume savings |
, | Log messages | MT or skip | These rarely face users |
, | UI labels, navigation | Human | Medium risk, must be natural |
, | Marketing copy, CTAs | Human (senior) | High risk, brand-critical |
, | Legal text | Human + legal review | Compliance-critical |
Step 5: Clean Up Dead Keys
Orphaned keys waste per-word costs and clutter the project.
import { readFileSync } from "fs"; async function findOrphanedKeys( projectId: string, sourceCodeDir: string ): Promise<string[]> { // Get all keys from Lokalise const allKeys: string[] = []; let cursor: string | undefined; do { const page = await lok.keys().list({ project_id: projectId, limit: 500, ...(cursor ? { cursor } : {}), }); for (const k of page.items) { allKeys.push(k.key_name.web ?? k.key_name.other ?? ""); } cursor = page.hasNextCursor() ? page.nextCursor() : undefined; } while (cursor); console.log(`Lokalise keys: ${allKeys.length}`); // Compare against source code references // (simplified — adjust grep pattern for your i18n framework) const { execSync } = await import("child_process"); const sourceRefs = execSync( `grep -roh "t(['\"][^'\"]*['\"])" ${sourceCodeDir} 2>/dev/null || true`, { encoding: "utf-8" } ) .split("\n") .map((line) => line.replace(/^t\(['"]/, "").replace(/['"]\)$/, "")) .filter(Boolean); const sourceKeySet = new Set(sourceRefs); const orphaned = allKeys.filter((k) => !sourceKeySet.has(k)); console.log(`Source code references: ${sourceKeySet.size}`); console.log(`Orphaned keys: ${orphaned.length}`); return orphaned; } // Archive orphaned keys to stop paying for their translations async function archiveKeys(projectId: string, keyNames: string[]): Promise<void> { // Look up key IDs for (const name of keyNames.slice(0, 50)) { const result = await lok.keys().list({ project_id: projectId, filter_keys: name, limit: 1, }); if (result.items.length > 0) { await lok.keys().update(result.items[0].key_id, { project_id: projectId, is_archived: true, }); } await new Promise((r) => setTimeout(r, 170)); // Rate limit } }
Step 6: Monitor Monthly Spend
set -euo pipefail echo "=== Monthly Cost Estimate ===" # Count total seats across teams SEAT_COUNT=$(curl -sf "https://api.lokalise.com/api2/teams" \ -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \ | jq '[.teams[].users_count] | add') # Estimate based on plan tier (adjust rate for your plan) RATE_PER_SEAT=120 # Essential plan — adjust to 290 for Pro MONTHLY_COST=$((SEAT_COUNT * RATE_PER_SEAT)) echo "Active seats: ${SEAT_COUNT}" echo "Estimated monthly cost: \$${MONTHLY_COST} (at \$${RATE_PER_SEAT}/seat)" echo "" echo "Cost reduction levers:" echo " 1. Remove inactive contributors (task-based access)" echo " 2. Use contributor groups instead of individual invites" echo " 3. Pre-translate with MT to reduce human translation volume" echo " 4. Archive orphaned keys to reduce per-word charges" echo " 5. Translate similar projects sequentially to maximize TM"
Output
- Usage audit report: projects, keys, languages, contributor seat count
- Inactive contributor identification for seat optimization
- TM leverage strategy (sequential translation, automation-enabled uploads)
- MT triage matrix mapping key prefixes to translation method
- Orphaned key detection and archival workflow
- Monthly cost estimate with reduction levers
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| High per-word costs | Human translating MT-suitable content | Apply MT to low-risk strings first |
| Seat costs growing | Adding contractors as full seats | Use task-based access: add when task opens, remove on close |
| TM not matching | Different key naming across projects | Standardize key names to improve TM reuse |
| Budget overrun | New languages added without planning | Budget per-language before adding to projects |
| Orphaned keys missed | Source code scan incomplete | Use multiple grep patterns matching your i18n framework |
Examples
Cost Comparison Scenarios
Solo project with 5 languages: 2 full-time translators + 8 freelancers. Move freelancers to task-based access. Seats drop from 10 to 2, saving ~$960/month.
Multi-app suite sharing terminology: Three apps share UI strings. Translate the largest first to seed TM, then translate the others. TM matches on shared strings cut human translation volume by 30-50%.
10,000-key project MT triage: Tag keys by content type. Apply MT to
tooltip.*, help.*, log.* prefixes (40% of keys). Route legal.*, marketing.*, ui.cta.* to humans. Saves ~$2,000 per target language.
Resources
- Lokalise Pricing Plans
- Lokalise API: Project Statistics
- Translation Memory in Lokalise
- Lokalise Machine Translation
- Keys API: List and Filter
Next Steps
For monitoring translation pipeline health and costs over time, see
lokalise-observability.