Optimize Lokalise costs through plan selection, usage monitoring, and efficiency. Use when analyzing Lokalise billing, reducing costs, or implementing usage monitoring and budget alerts. Trigger with phrases like "lokalise cost", "lokalise billing", "reduce lokalise costs", "lokalise pricing", "lokalise budget".
Optimizes Lokalise costs by analyzing usage, recommending plans, and implementing monitoring and cleanup.
/plugin marketplace add jeremylongshore/claude-code-plugins-plus-skills/plugin install lokalise-pack@claude-code-plugins-plusThis skill is limited to using the following tools:
Optimize Lokalise costs through smart plan selection, usage monitoring, and efficient workflows.
| Plan | Price | Included | Best For |
|---|---|---|---|
| Free | $0 | 1 project, 500 keys | Personal/testing |
| Essential | $120/mo | Unlimited projects, 2K keys/project | Small teams |
| Pro | $400/mo | Unlimited keys, OTA, Figma | Growing teams |
| Enterprise | Custom | SSO, dedicated support, SLA | Large organizations |
import { LokaliseApi } from "@lokalise/node-api";
async function analyzeUsage(): Promise<UsageReport> {
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
const projects = await client.projects().list();
const usage: UsageReport = {
totalProjects: projects.items.length,
totalKeys: 0,
totalLanguages: 0,
projectDetails: [],
};
for (const project of projects.items) {
const stats = project.statistics;
usage.totalKeys += stats.keys_total;
usage.totalLanguages += stats.languages;
usage.projectDetails.push({
name: project.name,
projectId: project.project_id,
keys: stats.keys_total,
languages: stats.languages,
baseWords: stats.base_words,
progress: stats.progress_total,
});
}
return usage;
}
interface UsageReport {
totalProjects: number;
totalKeys: number;
totalLanguages: number;
projectDetails: Array<{
name: string;
projectId: string;
keys: number;
languages: number;
baseWords: number;
progress: number;
}>;
}
function analyzeOptimizations(usage: UsageReport): Recommendation[] {
const recommendations: Recommendation[] = [];
// Check for unused projects
for (const project of usage.projectDetails) {
if (project.keys < 10) {
recommendations.push({
type: "unused_project",
severity: "low",
message: `Project "${project.name}" has only ${project.keys} keys. Consider archiving.`,
potentialSavings: "Reduce clutter, potential plan downgrade",
});
}
// Check for duplicate/similar keys
if (project.keys > 1000) {
recommendations.push({
type: "key_audit",
severity: "medium",
message: `Project "${project.name}" has ${project.keys} keys. Review for duplicates.`,
potentialSavings: "5-15% key reduction typical",
});
}
// Check for low-progress languages
if (project.progress < 50 && project.languages > 3) {
recommendations.push({
type: "unused_languages",
severity: "medium",
message: `Project "${project.name}" has ${project.languages} languages at ${project.progress}% progress.`,
potentialSavings: "Remove unused target languages",
});
}
}
// Plan recommendation
if (usage.totalKeys < 2000 && usage.totalProjects <= 3) {
recommendations.push({
type: "plan_downgrade",
severity: "high",
message: "Current usage fits Essential plan",
potentialSavings: "Potential $280/mo savings vs Pro",
});
}
return recommendations;
}
interface UsageMetrics {
date: Date;
keysCreated: number;
keysDeleted: number;
translationsUpdated: number;
mtCharacters: number; // Machine translation
apiCalls: number;
}
class LokaliseUsageMonitor {
private metrics: UsageMetrics[] = [];
trackApiCall(operation: string, details?: any) {
// Track API usage
console.log({
timestamp: new Date().toISOString(),
operation,
details,
});
}
async getMonthlyReport(): Promise<MonthlyReport> {
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
const projects = await client.projects().list();
let totalKeys = 0;
let totalMTCharacters = 0;
for (const project of projects.items) {
totalKeys += project.statistics.keys_total;
// Note: MT usage requires checking orders/billing API
}
return {
month: new Date().toISOString().slice(0, 7),
totalKeys,
estimatedCost: this.estimateCost(totalKeys),
};
}
private estimateCost(keys: number): number {
// Simplified estimation based on plan tiers
if (keys <= 500) return 0; // Free tier
if (keys <= 2000) return 120; // Essential
return 400; // Pro
}
}
async function cleanupUnusedKeys(projectId: string, dryRun = true) {
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
// Find archived keys
const archivedKeys = await client.keys().list({
project_id: projectId,
filter_archived: "include",
limit: 500,
});
const toDelete = archivedKeys.items.filter(k => k.is_archived);
console.log(`Found ${toDelete.length} archived keys`);
if (dryRun) {
console.log("DRY RUN - would delete:", toDelete.map(k => k.key_name.web));
return { deleted: 0, archived: toDelete.length };
}
// Actually delete
for (const key of toDelete) {
await client.keys().delete(key.key_id, { project_id: projectId });
}
return { deleted: toDelete.length };
}
async function removeUnusedLanguages(projectId: string, threshold = 10) {
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
const languages = await client.languages().list({ project_id: projectId });
const unused = languages.items.filter(
lang => (lang.statistics?.progress ?? 0) < threshold && !lang.is_default
);
console.log(`Languages below ${threshold}% progress:`,
unused.map(l => `${l.lang_name} (${l.statistics?.progress}%)`));
// Don't auto-delete - just report
return unused;
}
| Issue | Cause | Solution |
|---|---|---|
| Unexpected charges | Untracked MT usage | Monitor orders API |
| Key limit exceeded | Organic growth | Archive or delete unused |
| Overage fees | Wrong plan | Upgrade before hitting limits |
| Budget exceeded | No monitoring | Set up usage alerts |
const report = await analyzeUsage();
console.log(`Total Projects: ${report.totalProjects}`);
console.log(`Total Keys: ${report.totalKeys}`);
console.log(`Estimated Plan: ${report.totalKeys < 500 ? 'Free' : report.totalKeys < 2000 ? 'Essential' : 'Pro'}`);
// Lokalise MT pricing varies by provider and language pair
function estimateMTCost(
characters: number,
provider: "google" | "deepl" | "amazon" = "google"
): number {
const rates: Record<string, number> = {
google: 0.02, // $20 per million characters
deepl: 0.025, // $25 per million characters
amazon: 0.015, // $15 per million characters
};
return (characters / 1_000_000) * rates[provider];
}
async function checkBudget(budgetKeys: number): Promise<boolean> {
const usage = await analyzeUsage();
if (usage.totalKeys > budgetKeys * 0.9) {
console.warn(`WARNING: Approaching key limit (${usage.totalKeys}/${budgetKeys})`);
// Send alert
await sendSlackAlert({
channel: "#billing",
text: `Lokalise key usage at ${Math.round((usage.totalKeys / budgetKeys) * 100)}%`,
});
return false;
}
return true;
}
## Lokalise Cost Analysis
### Current Usage
- Projects: 5
- Total Keys: 3,500
- Languages: 8
### Current Plan: Pro ($400/mo)
### Recommendations
1. Archive 2 unused projects (-500 keys)
2. Remove 3 low-progress languages
3. Delete 200 archived keys
4. **Potential: Essential plan at $120/mo**
### Annual Savings: $3,360
For architecture patterns, see lokalise-reference-architecture.
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.