๐Ÿ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

lokalise-sdk-patterns

Apply production-ready Lokalise SDK patterns for TypeScript and Node.js. Use when implementing Lokalise integrations, refactoring SDK usage, or establishing team coding standards for Lokalise. Trigger with phrases like "lokalise SDK patterns", "lokalise best practices", "lokalise code patterns", "idiomatic lokalise". allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

lokalise-pack

Claude Code skill pack for Lokalise (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the lokalise-pack plugin:

/plugin install lokalise-pack@claude-code-plugins-plus

Click to copy

Instructions

# Lokalise SDK Patterns ## Overview Production-ready patterns for Lokalise SDK usage in TypeScript and Node.js applications. ## Prerequisites - Completed `lokalise-install-auth` setup - Familiarity with async/await and TypeScript - Understanding of error handling best practices ## Instructions ### Step 1: Implement Singleton Client Pattern ```typescript // src/lokalise/client.ts import { LokaliseApi } from "@lokalise/node-api"; let instance: LokaliseApi | null = null; export function getLokaliseClient(): LokaliseApi { if (!instance) { const apiKey = process.env.LOKALISE_API_TOKEN; if (!apiKey) { throw new Error("LOKALISE_API_TOKEN environment variable is required"); } instance = new LokaliseApi({ apiKey, enableCompression: true, // Enable gzip for large responses }); } return instance; } // Reset for testing export function resetLokaliseClient(): void { instance = null; } ``` ### Step 2: Add Error Handling Wrapper ```typescript // src/lokalise/errors.ts import { ApiError } from "@lokalise/node-api"; interface LokaliseResult { data: T | null; error: LokaliseError | null; } interface LokaliseError { code: number; message: string; retryable: boolean; } export async function safeLokaliseCall( operation: () => Promise ): Promise> { try { const data = await operation(); return { data, error: null }; } catch (err) { const error = parseLokaliseError(err); console.error("[Lokalise]", error); return { data: null, error }; } } function parseLokaliseError(err: unknown): LokaliseError { if (err instanceof ApiError) { return { code: err.code, message: err.message, retryable: [429, 500, 502, 503, 504].includes(err.code), }; } return { code: 0, message: err instanceof Error ? err.message : "Unknown error", retryable: false, }; } ``` ### Step 3: Implement Rate-Limited Queue ```typescript // src/lokalise/queue.ts import PQueue from "p-queue"; // Lokalise: 6 requests/sec, 10 concurrent per project const queue = new PQueue({ concurrency: 5, // Stay under concurrent limit interval: 1000, intervalCap: 5, // Stay under rate limit }); export async function queuedLokaliseCall( operation: () => Promise ): Promise { return queue.add(operation) as Promise; } // For batch operations export async function batchLokaliseOperations( items: T[], operation: (item: T) => Promise ): Promise { return Promise.all( items.map(item => queuedLokaliseCall(() => operation(item))) ); } ``` ### Step 4: Add Retry Logic ```typescript // src/lokalise/retry.ts export async function withRetry( operation: () => Promise, maxRetries = 3, baseDelayMs = 1000 ): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (err: any) { const isRetryable = err.code === 429 || (err.code >= 500 && err.code < 600); if (!isRetryable || attempt === maxRetries) { throw err; } // Exponential backoff with jitter const delay = baseDelayMs * Math.pow(2, attempt - 1); const jitter = Math.random() * 500; console.log(`[Lokalise] Retry ${attempt}/${maxRetries} in ${delay}ms...`); await new Promise(r => setTimeout(r, delay + jitter)); } } throw new Error("Unreachable"); } ``` ### Step 5: Implement Cursor Pagination Helper ```typescript // src/lokalise/pagination.ts import { LokaliseApi, PaginatedResult } from "@lokalise/node-api"; export async function* paginateKeys( client: LokaliseApi, projectId: string, options: { limit?: number } = {} ): AsyncGenerator { const limit = options.limit || 500; // Max 500 as of 2025 let cursor: string | undefined; do { const result = await client.keys().list({ project_id: projectId, limit, pagination: "cursor", cursor, }); for (const key of result.items) { yield key; } cursor = result.hasNextCursor() ? result.nextCursor : undefined; } while (cursor); } // Usage async function getAllKeys(projectId: string) { const client = getLokaliseClient(); const keys: any[] = []; for await (const key of paginateKeys(client, projectId)) { keys.push(key); } return keys; } ``` ## Output - Type-safe client singleton with compression - Robust error handling with retryable detection - Rate-limited request queue - Automatic retry with exponential backoff - Cursor pagination for large datasets ## Error Handling | Pattern | Use Case | Benefit | |---------|----------|---------| | Safe wrapper | All API calls | Prevents uncaught exceptions | | Request queue | Bulk operations | Respects rate limits | | Retry logic | Transient failures | Improves reliability | | Pagination | Large datasets | Memory efficient | ## Examples ### Factory Pattern (Multi-Project) ```typescript const clients = new Map(); export function getClientForProject(projectId: string): LokaliseApi { if (!clients.has(projectId)) { // Could use different tokens per project if needed clients.set(projectId, new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN!, enableCompression: true, })); } return clients.get(projectId)!; } ``` ### Typed Response Wrapper ```typescript import { Key, Translation, Project } from "@lokalise/node-api"; interface LokaliseService { getProject(id: string): Promise; listKeys(projectId: string): Promise; updateTranslation(projectId: string, translationId: number, text: string): Promise; } export const lokaliseService: LokaliseService = { async getProject(id) { const client = getLokaliseClient(); return client.projects().get(id); }, async listKeys(projectId) { const client = getLokaliseClient(); const result = await client.keys().list({ project_id: projectId }); return result.items; }, async updateTranslation(projectId, translationId, text) { const client = getLokaliseClient(); return client.translations().update(translationId, { project_id: projectId, translation: text, }); }, }; ``` ### Branch-Aware Client ```typescript export function getProjectWithBranch(projectId: string, branch?: string): string { // Lokalise branch syntax: projectId:branchName return branch ? `${projectId}:${branch}` : projectId; } // Usage const projectId = getProjectWithBranch("123456.abcdef", "feature/new-ui"); const keys = await client.keys().list({ project_id: projectId }); ``` ## Resources - [Node SDK Documentation](https://lokalise.github.io/node-lokalise-api/) - [Pagination Guide](https://lokalise.github.io/node-lokalise-api/api/getting-started.html) - [API Error Codes](https://developers.lokalise.com/reference/api-errors) ## Next Steps Apply patterns in `lokalise-core-workflow-a` for real-world usage.

Skill file: plugins/saas-packs/lokalise-pack/skills/lokalise-sdk-patterns/SKILL.md